@dudousxd/nestjs-agent-core 0.12.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -22,6 +22,9 @@ var AGENT_PROMPT_CONTRIBUTORS = Symbol.for("@dudousxd/nestjs-agent:prompt-contri
22
22
  var AGENT_ACTOR_DIRECTORY = Symbol.for("@dudousxd/nestjs-agent:actor-directory");
23
23
  var AGENT_ATTACHMENT_STAGING = Symbol.for("@dudousxd/nestjs-agent:attachment-staging");
24
24
  var AGENT_APPROVAL_PORT = Symbol.for("@dudousxd/nestjs-agent:approval-port");
25
+ var AGENT_SKILLS = Symbol.for("@dudousxd/nestjs-agent:skills");
26
+ var AGENT_MEMORY = Symbol.for("@dudousxd/nestjs-agent:memory");
27
+ var AGENT_SKILL_SOURCES = Symbol.for("@dudousxd/nestjs-agent:skill-sources");
25
28
 
26
29
  // src/spi/token-stream-sink.ts
27
30
  var AgentStreamError = class extends Error {
@@ -43,6 +46,17 @@ function encodeStreamEvent(event) {
43
46
  `);
44
47
  }
45
48
  __name(encodeStreamEvent, "encodeStreamEvent");
49
+ function decodeStreamEvent(line) {
50
+ try {
51
+ const parsed = JSON.parse(line);
52
+ if (typeof parsed === "object" && parsed !== null && typeof parsed.kind === "string") {
53
+ return parsed;
54
+ }
55
+ } catch {
56
+ }
57
+ return null;
58
+ }
59
+ __name(decodeStreamEvent, "decodeStreamEvent");
46
60
 
47
61
  // src/spi/pricing-store.ts
48
62
  async function seedModelPrices(store, prices) {
@@ -52,6 +66,40 @@ async function seedModelPrices(store, prices) {
52
66
  }
53
67
  __name(seedModelPrices, "seedModelPrices");
54
68
 
69
+ // src/spi/processors.ts
70
+ var DEFAULT_INCREMENTAL_LOOKBACK_CHARS = 64;
71
+ var OutputRejectedError = class extends Error {
72
+ static {
73
+ __name(this, "OutputRejectedError");
74
+ }
75
+ /** {@link OutputProcessor.name} of the processor that refused. */
76
+ processor;
77
+ /** The reason it gave, verbatim. */
78
+ reason;
79
+ constructor(processor, reason) {
80
+ super(`Output rejected by "${processor}": ${reason}`);
81
+ this.name = "OutputRejectedError";
82
+ this.processor = processor;
83
+ this.reason = reason;
84
+ }
85
+ };
86
+ var ProcessorFailedError = class extends Error {
87
+ static {
88
+ __name(this, "ProcessorFailedError");
89
+ }
90
+ /** Which seam it was on, so a reader knows whether the prompt or the answer was in flight. */
91
+ phase;
92
+ /** The processor's `name`. */
93
+ processor;
94
+ constructor(phase, processor, cause) {
95
+ super(`${phase} processor "${processor}" failed: ${cause instanceof Error ? cause.message : String(cause)}`);
96
+ this.name = "ProcessorFailedError";
97
+ this.phase = phase;
98
+ this.processor = processor;
99
+ this.cause = cause;
100
+ }
101
+ };
102
+
55
103
  // src/spi/governance-queries.ts
56
104
  var THREAD_DETAIL_CONTENT_CHARS = 2e3;
57
105
  function truncateDetailContent(content) {
@@ -268,464 +316,2779 @@ function filterToolsByAllowList(tools, allowedTools) {
268
316
  }
269
317
  __name(filterToolsByAllowList, "filterToolsByAllowList");
270
318
 
271
- // src/agent-registry.ts
272
- var AgentRegistry = class {
273
- static {
274
- __name(this, "AgentRegistry");
319
+ // src/history.ts
320
+ function estimateMessageTokens(message) {
321
+ const extras = (message.toolCalls !== void 0 ? JSON.stringify(message.toolCalls).length : 0) + (message.toolResults !== void 0 ? JSON.stringify(message.toolResults).length : 0);
322
+ return Math.ceil((message.content.length + extras) / 4) + 4;
323
+ }
324
+ __name(estimateMessageTokens, "estimateMessageTokens");
325
+ function windowHistory(options) {
326
+ const estimate = options.estimate ?? estimateMessageTokens;
327
+ const policy = {
328
+ // A suffix selection of at most `maxMessages` rows, which is exactly what `HistoryPolicy`'s row
329
+ // hint promises — so the store can bound its read to that many of the thread's newest messages.
330
+ // A token-only window declares nothing: its budget names no row count.
331
+ ...options.maxMessages !== void 0 ? {
332
+ maxMessages: options.maxMessages
333
+ } : {},
334
+ select(messages) {
335
+ let cut = 0;
336
+ if (options.maxMessages !== void 0) {
337
+ cut = Math.max(cut, messages.length - options.maxMessages);
338
+ }
339
+ if (options.maxTokens !== void 0) {
340
+ cut = Math.max(cut, tokenCut(messages, options.maxTokens, estimate));
341
+ }
342
+ cut = Math.min(cut, Math.max(0, messages.length - 1));
343
+ return {
344
+ keep: messages.slice(cut),
345
+ drop: messages.slice(0, cut)
346
+ };
347
+ }
348
+ };
349
+ return options.summarize !== void 0 ? {
350
+ ...policy,
351
+ summarize: options.summarize
352
+ } : policy;
353
+ }
354
+ __name(windowHistory, "windowHistory");
355
+ function tokenCut(messages, maxTokens, estimate) {
356
+ let index = messages.length;
357
+ let total = 0;
358
+ while (index > 0) {
359
+ const message = messages[index - 1];
360
+ if (message === void 0) {
361
+ break;
362
+ }
363
+ const cost = estimate(message);
364
+ if (index < messages.length && total + cost > maxTokens) {
365
+ break;
366
+ }
367
+ total += cost;
368
+ index -= 1;
275
369
  }
276
- definitions = /* @__PURE__ */ new Map();
277
- register(definition) {
278
- this.definitions.set(definition.name, definition);
370
+ return index;
371
+ }
372
+ __name(tokenCut, "tokenCut");
373
+ var DEFAULT_HISTORY_SUMMARY_INSTRUCTION = "Summarize this conversation for an assistant that will continue it without seeing these messages. Preserve decisions made, facts and constraints the user stated, identifiers and names mentioned, and anything left unresolved. Omit pleasantries. Reply with the summary only \u2014 no preamble, no headings.";
374
+ function summarizeWithModel(model, instruction = DEFAULT_HISTORY_SUMMARY_INSTRUCTION) {
375
+ return async (dropped) => {
376
+ const discard = {
377
+ write: /* @__PURE__ */ __name(() => {
378
+ }, "write"),
379
+ end: /* @__PURE__ */ __name(() => {
380
+ }, "end"),
381
+ fail: /* @__PURE__ */ __name(() => {
382
+ }, "fail")
383
+ };
384
+ const turn = await model.runTurn({
385
+ system: instruction,
386
+ messages: dropped,
387
+ tools: [],
388
+ sink: discard
389
+ });
390
+ return {
391
+ text: turn.text,
392
+ usage: turn.usage,
393
+ ...turn.modelId !== void 0 ? {
394
+ modelId: turn.modelId
395
+ } : {}
396
+ };
397
+ };
398
+ }
399
+ __name(summarizeWithModel, "summarizeWithModel");
400
+
401
+ // src/processors.ts
402
+ var decoder = new TextDecoder();
403
+ function createFrameBuffer() {
404
+ const frames = [];
405
+ return {
406
+ writer: {
407
+ write: /* @__PURE__ */ __name((chunk) => {
408
+ for (const line of decoder.decode(chunk).split("\n")) {
409
+ if (line.length > 0) {
410
+ frames.push(line);
411
+ }
412
+ }
413
+ }, "write"),
414
+ end: /* @__PURE__ */ __name(() => {
415
+ }, "end"),
416
+ fail: /* @__PURE__ */ __name(() => {
417
+ }, "fail")
418
+ },
419
+ frames: /* @__PURE__ */ __name(() => frames, "frames")
420
+ };
421
+ }
422
+ __name(createFrameBuffer, "createFrameBuffer");
423
+ function releaseGatedFrames(frames, text) {
424
+ const released = [];
425
+ let emitted = false;
426
+ for (const frame of frames) {
427
+ const event = decodeStreamEvent(frame);
428
+ if (event === null || event.kind === "text") {
429
+ if (!emitted && text.length > 0) {
430
+ released.push(encodeStreamEvent({
431
+ kind: "text",
432
+ text
433
+ }));
434
+ emitted = true;
435
+ }
436
+ continue;
437
+ }
438
+ released.push(encodeStreamEvent(event));
279
439
  }
280
- get(name) {
281
- return this.definitions.get(name);
440
+ if (!emitted && text.length > 0) {
441
+ released.push(encodeStreamEvent({
442
+ kind: "text",
443
+ text
444
+ }));
282
445
  }
283
- has(name) {
284
- return this.definitions.has(name);
446
+ return released;
447
+ }
448
+ __name(releaseGatedFrames, "releaseGatedFrames");
449
+ async function runInputProcessors(processors, prompt, ctx) {
450
+ let current = prompt;
451
+ for (const processor of processors) {
452
+ try {
453
+ current = await processor.process(current, ctx);
454
+ } catch (error) {
455
+ throw new ProcessorFailedError("input", processor.name, error);
456
+ }
285
457
  }
286
- list() {
287
- return [
288
- ...this.definitions.values()
289
- ];
458
+ return current;
459
+ }
460
+ __name(runInputProcessors, "runInputProcessors");
461
+ async function runOutputProcessors(processors, answer, ctx) {
462
+ let text = answer.text;
463
+ for (const processor of processors) {
464
+ let verdict;
465
+ try {
466
+ verdict = await processor.process({
467
+ text,
468
+ toolCalls: answer.toolCalls
469
+ }, ctx);
470
+ } catch (error) {
471
+ throw new ProcessorFailedError("output", processor.name, error);
472
+ }
473
+ if (verdict.action === "reject") {
474
+ return {
475
+ text,
476
+ rejection: {
477
+ processor: processor.name,
478
+ reason: verdict.reason
479
+ }
480
+ };
481
+ }
482
+ if (verdict.action === "replace") {
483
+ text = verdict.text;
484
+ }
290
485
  }
291
- };
292
-
293
- // src/tool-registry.ts
294
- var ToolForbiddenError = class extends Error {
295
- static {
296
- __name(this, "ToolForbiddenError");
486
+ return {
487
+ text
488
+ };
489
+ }
490
+ __name(runOutputProcessors, "runOutputProcessors");
491
+ async function gateFollowUps(processors, followUps, ctx) {
492
+ const kept = [];
493
+ for (const followUp of followUps) {
494
+ const settled = await runOutputProcessors(processors, {
495
+ text: followUp,
496
+ toolCalls: []
497
+ }, ctx);
498
+ if (settled.rejection === void 0 && settled.text.length > 0) {
499
+ kept.push(settled.text);
500
+ }
297
501
  }
298
- toolName;
299
- constructor(toolName) {
300
- super(`Tool "${toolName}" is not allowed for this role`), this.toolName = toolName;
301
- this.name = "ToolForbiddenError";
502
+ return kept;
503
+ }
504
+ __name(gateFollowUps, "gateFollowUps");
505
+ function resolveOutputGateMode(processors) {
506
+ if (processors.length === 0) {
507
+ return "off";
302
508
  }
303
- };
304
- var ToolDisabledError = class extends Error {
305
- static {
306
- __name(this, "ToolDisabledError");
509
+ return processors.every((processor) => processor.incremental !== void 0) ? "incremental" : "whole";
510
+ }
511
+ __name(resolveOutputGateMode, "resolveOutputGateMode");
512
+ function resolveGateLookback(processors) {
513
+ let lookback = 0;
514
+ for (const processor of processors) {
515
+ if (processor.incremental !== void 0) {
516
+ lookback = Math.max(lookback, processor.incremental.lookbackChars ?? DEFAULT_INCREMENTAL_LOOKBACK_CHARS);
517
+ }
307
518
  }
308
- toolName;
309
- constructor(toolName) {
310
- super(`Tool "${toolName}" is disabled in this deployment`), this.toolName = toolName;
311
- this.name = "ToolDisabledError";
519
+ return lookback;
520
+ }
521
+ __name(resolveGateLookback, "resolveGateLookback");
522
+ function createIncrementalGate(options) {
523
+ const { processors, ctx, lookbackChars, writer } = options;
524
+ let raw = "";
525
+ let released = "";
526
+ let rejection;
527
+ let stalled = false;
528
+ const toolCalls = [];
529
+ let queue = Promise.resolve();
530
+ async function advance() {
531
+ if (raw.length === 0) {
532
+ return;
533
+ }
534
+ let settled;
535
+ try {
536
+ settled = await runOutputProcessors(processors, {
537
+ text: raw,
538
+ toolCalls
539
+ }, ctx);
540
+ } catch {
541
+ stalled = true;
542
+ return;
543
+ }
544
+ if (settled.rejection !== void 0) {
545
+ rejection = settled.rejection;
546
+ return;
547
+ }
548
+ const candidate = settled.text.slice(0, Math.max(0, settled.text.length - lookbackChars));
549
+ if (candidate.length <= released.length || !candidate.startsWith(released)) {
550
+ return;
551
+ }
552
+ const delta = candidate.slice(released.length);
553
+ released = candidate;
554
+ await writer.write(encodeStreamEvent({
555
+ kind: "text",
556
+ text: delta
557
+ }));
312
558
  }
313
- };
314
- var ToolNotFoundError = class extends Error {
315
- static {
316
- __name(this, "ToolNotFoundError");
559
+ __name(advance, "advance");
560
+ async function handle(chunk) {
561
+ for (const line of decoder.decode(chunk).split("\n")) {
562
+ if (line.length === 0) {
563
+ continue;
564
+ }
565
+ const event = decodeStreamEvent(line);
566
+ if (event === null || rejection !== void 0) {
567
+ continue;
568
+ }
569
+ if (event.kind === "text") {
570
+ raw += event.text;
571
+ if (!stalled) {
572
+ await advance();
573
+ }
574
+ continue;
575
+ }
576
+ if (event.kind === "tool-input-available") {
577
+ toolCalls.push({
578
+ id: event.id,
579
+ name: event.name,
580
+ input: event.input
581
+ });
582
+ }
583
+ await writer.write(encodeStreamEvent(event));
584
+ }
317
585
  }
318
- toolName;
319
- constructor(toolName) {
320
- super(`Tool "${toolName}" is not registered`), this.toolName = toolName;
321
- this.name = "ToolNotFoundError";
586
+ __name(handle, "handle");
587
+ return {
588
+ writer: {
589
+ write: /* @__PURE__ */ __name((chunk) => {
590
+ queue = queue.then(() => handle(chunk));
591
+ return queue;
592
+ }, "write"),
593
+ end: /* @__PURE__ */ __name(() => {
594
+ }, "end"),
595
+ fail: /* @__PURE__ */ __name(() => {
596
+ }, "fail")
597
+ },
598
+ settled: /* @__PURE__ */ __name(() => queue, "settled"),
599
+ released: /* @__PURE__ */ __name(() => released, "released"),
600
+ rejection: /* @__PURE__ */ __name(() => rejection, "rejection")
601
+ };
602
+ }
603
+ __name(createIncrementalGate, "createIncrementalGate");
604
+ function gateTail(processors, released, text) {
605
+ if (!text.startsWith(released)) {
606
+ throw new ProcessorFailedError("output", processors.map((processor) => processor.name).join(" \u2192 "), new Error(`the gated answer is not an extension of the ${released.length} characters this chain already released \u2014 a processor that declares "incremental" promises its result on a prefix stays a prefix of its result on the whole answer, outside the last lookbackChars`));
322
607
  }
323
- };
324
- var ToolInputInvalidError = class extends Error {
608
+ return text.slice(released.length);
609
+ }
610
+ __name(gateTail, "gateTail");
611
+
612
+ // src/structured-output.ts
613
+ var StructuredOutputError = class extends Error {
325
614
  static {
326
- __name(this, "ToolInputInvalidError");
615
+ __name(this, "StructuredOutputError");
327
616
  }
328
- toolName;
617
+ /** Why it failed the schema. Empty only when the text was not JSON at all. */
329
618
  issues;
330
- constructor(toolName, issues) {
331
- super(`Invalid input for tool "${toolName}": ${issues.map((issue) => issue.message).join("; ")}`), this.toolName = toolName, this.issues = issues;
332
- this.name = "ToolInputInvalidError";
619
+ /** The last text the model produced, verbatim. */
620
+ text;
621
+ /** How many model calls were spent trying (1 = the formatting pass, no repairs). */
622
+ attempts;
623
+ constructor(issues, text, attempts) {
624
+ const detail = issues.length > 0 ? issues.map((issue4) => issue4.message).join("; ") : "the model did not reply with JSON";
625
+ super(`Structured output invalid after ${attempts} attempt(s): ${detail}`);
626
+ this.name = "StructuredOutputError";
627
+ this.issues = issues;
628
+ this.text = text;
629
+ this.attempts = attempts;
333
630
  }
334
631
  };
335
- var ToolRegistry = class {
336
- static {
337
- __name(this, "ToolRegistry");
338
- }
339
- entries = /* @__PURE__ */ new Map();
340
- register(spec, handler) {
341
- this.entries.set(spec.name, {
342
- spec,
343
- handler
344
- });
345
- }
346
- has(name) {
347
- return this.entries.has(name);
348
- }
349
- spec(name) {
350
- return this.entries.get(name)?.spec;
351
- }
352
- allSpecs() {
353
- return [
354
- ...this.entries.values()
355
- ].map((entry) => entry.spec);
356
- }
357
- /**
358
- * The tools to offer the model for this actor+agent, after the four filter layers: what this
359
- * deployment has enabled, what this actor's role allows, what each tool's own `canUse` allows
360
- * this actor, and finally what this agent pinned.
361
- *
362
- * Every layer only ever removes tools, so no arrangement of them can widen what a turn reaches.
363
- */
364
- async definitionsFor(actor, policy, allowedTools) {
365
- const live = await filterToolsByEnabled([
366
- ...this.entries.values()
367
- ]);
368
- const allowedByRole = new Set((await filterToolsByRole(live.map((entry) => entry.spec), actor, policy)).map((spec) => spec.name));
369
- const roleScoped = live.filter((entry) => allowedByRole.has(entry.spec.name));
370
- const actorScoped = await filterToolsByCanUse(roleScoped, actor);
371
- const allowScoped = filterToolsByAllowList(actorScoped.map((entry) => entry.spec), allowedTools);
372
- return allowScoped.map((spec) => ({
373
- name: spec.name,
374
- kind: spec.kind,
375
- description: spec.description,
376
- inputSchema: spec.inputSchema
377
- }));
378
- }
379
- /**
380
- * Run a tool. Re-checks that the tool is enabled and that the role allows it (defense-in-depth —
381
- * a call can reach here from a replayed durable step or an approval granted before the flag
382
- * moved, neither of which went through `definitionsFor` again) and re-parses the input via Zod.
383
- */
384
- async invoke(name, input, ctx, policy) {
385
- const entry = this.entries.get(name);
386
- if (entry === void 0) {
387
- throw new ToolNotFoundError(name);
388
- }
389
- if (!await isToolEnabled(entry.spec, entry.handler)) {
390
- throw new ToolDisabledError(name);
391
- }
392
- if (!await policy.can(ctx.actor, entry.spec)) {
393
- throw new ToolForbiddenError(name);
394
- }
395
- if (!await canActorUseTool(ctx.actor, entry.handler)) {
396
- throw new ToolForbiddenError(name);
632
+ function extractJson(text) {
633
+ const candidates = [
634
+ text,
635
+ text.match(/\{[\s\S]*\}/)?.[0],
636
+ text.match(/\[[\s\S]*\]/)?.[0]
637
+ ];
638
+ for (const candidate of candidates) {
639
+ if (candidate === void 0) {
640
+ continue;
397
641
  }
398
- const validation = await entry.spec.inputSchema["~standard"].validate(input);
399
- if (validation.issues !== void 0) {
400
- throw new ToolInputInvalidError(name, validation.issues);
642
+ try {
643
+ return JSON.parse(candidate);
644
+ } catch {
401
645
  }
402
- return entry.handler.execute(validation.value, ctx);
403
- }
404
- };
405
- var DefaultRolesPolicy = class {
406
- static {
407
- __name(this, "DefaultRolesPolicy");
408
- }
409
- defaultRoles;
410
- constructor(defaultRoles = [
411
- "ADMIN"
412
- ]) {
413
- this.defaultRoles = defaultRoles;
414
646
  }
415
- can(actor, tool) {
416
- const allowed = tool.roles ?? this.defaultRoles;
417
- return (actor.roles ?? []).some((role) => allowed.includes(role));
647
+ return void 0;
648
+ }
649
+ __name(extractJson, "extractJson");
650
+ async function validateStructured(schema, text, reported) {
651
+ const candidate = reported !== void 0 ? reported : extractJson(text);
652
+ if (candidate === void 0) {
653
+ return {
654
+ ok: false,
655
+ issues: []
656
+ };
418
657
  }
419
- };
658
+ const result = await schema["~standard"].validate(candidate);
659
+ return result.issues !== void 0 ? {
660
+ ok: false,
661
+ issues: result.issues
662
+ } : {
663
+ ok: true,
664
+ value: result.value
665
+ };
666
+ }
667
+ __name(validateStructured, "validateStructured");
668
+ var DEFAULT_STRUCTURED_OUTPUT_INSTRUCTION = "Restate the assistant's final answer as a single JSON value matching the required schema. Use only information already present in the conversation \u2014 add nothing, and answer nothing that was not asked. Reply with ONLY the JSON: no prose, no explanation, no code fences.";
669
+ function repairInstruction(instruction, issues) {
670
+ const detail = issues.length > 0 ? issues.map((issue4) => `${(issue4.path ?? []).join(".") || "(root)"}: ${issue4.message}`).join("\n") : "the previous reply was not valid JSON";
671
+ return `${instruction}
420
672
 
421
- // src/agent-loop.ts
422
- import { createHash } from "node:crypto";
423
- import { trace } from "@dudousxd/nestjs-diagnostics";
673
+ Your previous reply was rejected:
674
+ ${detail}`;
675
+ }
676
+ __name(repairInstruction, "repairInstruction");
424
677
 
425
- // src/diagnostics.ts
426
- import { emit } from "@dudousxd/nestjs-diagnostics";
427
- function publishAgentRunStarted(payload) {
428
- emit("agent", "run.started", payload);
678
+ // src/elicitation.ts
679
+ function optionValues(question) {
680
+ return new Set(question.options.map((option) => option.value));
429
681
  }
430
- __name(publishAgentRunStarted, "publishAgentRunStarted");
431
- function publishAgentMessage(payload) {
432
- emit("agent", "message", payload);
682
+ __name(optionValues, "optionValues");
683
+ function normalizeElicitationReply(reply) {
684
+ if (typeof reply !== "object" || reply === null) {
685
+ return {
686
+ answers: {}
687
+ };
688
+ }
689
+ const candidate = reply;
690
+ if (typeof candidate.answers === "object" && candidate.answers !== null) {
691
+ return reply;
692
+ }
693
+ const answeredByRef = candidate.answeredByRef ?? candidate.executedByRef;
694
+ return {
695
+ answers: {},
696
+ ...candidate.approved === false || candidate.skipped === true ? {
697
+ skipped: true
698
+ } : {},
699
+ ...answeredByRef !== void 0 ? {
700
+ answeredByRef
701
+ } : {}
702
+ };
433
703
  }
434
- __name(publishAgentMessage, "publishAgentMessage");
435
- function publishAgentToolCall(payload) {
436
- emit("agent", "tool-call", payload);
704
+ __name(normalizeElicitationReply, "normalizeElicitationReply");
705
+ function resolveElicitation(request, raw) {
706
+ const reply = normalizeElicitationReply(raw);
707
+ const answers = {};
708
+ const defaulted = [];
709
+ for (const question of request.questions) {
710
+ const submitted = reply.skipped === true ? void 0 : reply.answers[question.id];
711
+ if (submitted === void 0) {
712
+ answers[question.id] = [
713
+ ...question.defaults ?? []
714
+ ];
715
+ defaulted.push(question.id);
716
+ continue;
717
+ }
718
+ const allowed = optionValues(question);
719
+ const valid = question.allowFreeText === true ? submitted : submitted.filter((value) => allowed.has(value));
720
+ answers[question.id] = question.multiple === true ? valid : valid.slice(0, 1);
721
+ }
722
+ return {
723
+ answers,
724
+ skipped: reply.skipped === true,
725
+ defaulted
726
+ };
437
727
  }
438
- __name(publishAgentToolCall, "publishAgentToolCall");
439
- function publishAgentQuotaExceeded(payload) {
440
- emit("agent", "quota.exceeded", payload);
728
+ __name(resolveElicitation, "resolveElicitation");
729
+ function settleElicitation(request, reply) {
730
+ const outcome = resolveElicitation(request, reply);
731
+ return {
732
+ ...outcome,
733
+ summary: renderElicitationAnswers(request, outcome)
734
+ };
441
735
  }
442
- __name(publishAgentQuotaExceeded, "publishAgentQuotaExceeded");
443
- function publishAgentRunFinished(payload) {
444
- emit("agent", "run.finished", payload);
736
+ __name(settleElicitation, "settleElicitation");
737
+ function renderElicitationAnswers(request, outcome) {
738
+ const lines = request.questions.map((question) => {
739
+ const chosen = outcome.answers[question.id] ?? [];
740
+ const labels = chosen.map((value) => question.options.find((option) => option.value === value)?.label ?? value);
741
+ return `${question.prompt} \u2192 ${labels.length > 0 ? labels.join(", ") : "(no answer)"}`;
742
+ });
743
+ const preface = outcome.skipped ? "The user declined to answer and asked you to proceed on these assumptions:" : "The user answered:";
744
+ return `${preface}
745
+ ${lines.join("\n")}`;
445
746
  }
446
- __name(publishAgentRunFinished, "publishAgentRunFinished");
447
- function publishAgentRunFailed(payload) {
448
- emit("agent", "run.failed", payload);
449
- }
450
- __name(publishAgentRunFailed, "publishAgentRunFailed");
451
- function publishAgentDelegated(payload) {
452
- emit("agent", "delegated", payload);
453
- }
454
- __name(publishAgentDelegated, "publishAgentDelegated");
455
- function publishAgentRetrieved(payload) {
456
- emit("agent", "retrieved", payload);
457
- }
458
- __name(publishAgentRetrieved, "publishAgentRetrieved");
459
- function publishAgentToolRetry(payload) {
460
- emit("agent", "tool.retry", payload);
461
- }
462
- __name(publishAgentToolRetry, "publishAgentToolRetry");
463
- var AGENT_SPAN_EVENTS = [
464
- "llm.turn",
465
- "tool.execution",
466
- "retrieval",
467
- "follow-ups"
468
- ];
469
- var AGENT_DIAGNOSTIC_EVENTS = [
470
- "run.started",
471
- "message",
472
- "tool-call",
473
- "quota.exceeded",
474
- "run.finished",
475
- "run.failed",
476
- "delegated",
477
- "retrieved",
478
- "tool.retry"
479
- ];
480
- function agentDiagnosticKey(event) {
481
- return `agent:${event}`;
747
+ __name(renderElicitationAnswers, "renderElicitationAnswers");
748
+ var ASK_TOOL_NAME = "ask";
749
+ var MAX_ASK_QUESTIONS = 5;
750
+ function issue(path, message) {
751
+ return {
752
+ message,
753
+ path
754
+ };
482
755
  }
483
- __name(agentDiagnosticKey, "agentDiagnosticKey");
484
-
485
- // src/tool-retry.ts
486
- function hasTransientShape(error) {
487
- if (typeof error !== "object" || error === null) {
488
- return false;
756
+ __name(issue, "issue");
757
+ function parseOption(raw, path, issues) {
758
+ if (typeof raw !== "object" || raw === null) {
759
+ issues.push(issue(path, "must be an object"));
760
+ return void 0;
489
761
  }
490
- const code = "code" in error ? error.code : void 0;
491
- const errno = "errno" in error ? error.errno : void 0;
492
- const sqlState = "sqlState" in error ? error.sqlState : void 0;
493
- if (code === 1213 || code === 1205 || errno === 1213 || errno === 1205) {
494
- return true;
762
+ const candidate = raw;
763
+ if (typeof candidate.value !== "string" || candidate.value.length === 0) {
764
+ issues.push(issue([
765
+ ...path,
766
+ "value"
767
+ ], "must be a non-empty string"));
768
+ return void 0;
495
769
  }
496
- if (code === "ER_LOCK_DEADLOCK" || code === "ER_LOCK_WAIT_TIMEOUT") {
497
- return true;
770
+ if (typeof candidate.label !== "string" || candidate.label.length === 0) {
771
+ issues.push(issue([
772
+ ...path,
773
+ "label"
774
+ ], "must be a non-empty string"));
775
+ return void 0;
498
776
  }
499
- if (code === "40001" || code === "40P01" || sqlState === "40001" || sqlState === "40P01") {
500
- return true;
777
+ return {
778
+ value: candidate.value,
779
+ label: candidate.label,
780
+ ...typeof candidate.hotkey === "string" ? {
781
+ hotkey: candidate.hotkey
782
+ } : {}
783
+ };
784
+ }
785
+ __name(parseOption, "parseOption");
786
+ function parseQuestion(raw, path, issues) {
787
+ if (typeof raw !== "object" || raw === null) {
788
+ issues.push(issue(path, "must be an object"));
789
+ return void 0;
501
790
  }
502
- if (code === "SQLITE_BUSY") {
503
- return true;
791
+ const candidate = raw;
792
+ if (typeof candidate.id !== "string" || candidate.id.length === 0) {
793
+ issues.push(issue([
794
+ ...path,
795
+ "id"
796
+ ], "must be a non-empty string"));
797
+ return void 0;
504
798
  }
505
- const message = "message" in error ? error.message : void 0;
506
- return typeof message === "string" && /deadlock|lock wait timeout|serialization failure/i.test(message);
507
- }
508
- __name(hasTransientShape, "hasTransientShape");
509
- function isTransientToolError(error) {
510
- if (hasTransientShape(error)) {
511
- return true;
799
+ if (typeof candidate.prompt !== "string" || candidate.prompt.length === 0) {
800
+ issues.push(issue([
801
+ ...path,
802
+ "prompt"
803
+ ], "must be a non-empty string"));
804
+ return void 0;
512
805
  }
513
- if (typeof error === "object" && error !== null && "cause" in error) {
514
- const cause = error.cause;
515
- if (cause !== void 0 && cause !== error && hasTransientShape(cause)) {
516
- return true;
806
+ if (!Array.isArray(candidate.options) || candidate.options.length === 0) {
807
+ issues.push(issue([
808
+ ...path,
809
+ "options"
810
+ ], "must be a non-empty array"));
811
+ return void 0;
812
+ }
813
+ const options = [];
814
+ for (const [index, rawOption] of candidate.options.entries()) {
815
+ const option = parseOption(rawOption, [
816
+ ...path,
817
+ "options",
818
+ index
819
+ ], issues);
820
+ if (option !== void 0) {
821
+ options.push(option);
517
822
  }
518
823
  }
519
- return false;
520
- }
521
- __name(isTransientToolError, "isTransientToolError");
522
- var DEFAULT_TOOL_TRANSIENT_RETRY_ATTEMPTS = 2;
523
- var DEFAULT_TOOL_TRANSIENT_RETRY_BACKOFF_MS = 150;
524
- function resolveToolTransientRetryNumbers(setting) {
525
- if (setting === false) {
526
- return false;
824
+ if (!Array.isArray(candidate.defaults) || candidate.defaults.length === 0) {
825
+ issues.push(issue([
826
+ ...path,
827
+ "defaults"
828
+ ], "must pre-pick at least one option \u2014 say what you would choose so the user can just confirm"));
829
+ return void 0;
830
+ }
831
+ const offered = new Set(options.map((option) => option.value));
832
+ const defaults = candidate.defaults.filter((value) => typeof value === "string" && offered.has(value));
833
+ if (defaults.length === 0) {
834
+ issues.push(issue([
835
+ ...path,
836
+ "defaults"
837
+ ], "must name values that appear in this question's options"));
838
+ return void 0;
527
839
  }
528
840
  return {
529
- attempts: setting?.attempts ?? DEFAULT_TOOL_TRANSIENT_RETRY_ATTEMPTS,
530
- backoffMs: setting?.backoffMs ?? DEFAULT_TOOL_TRANSIENT_RETRY_BACKOFF_MS
841
+ id: candidate.id,
842
+ prompt: candidate.prompt,
843
+ options,
844
+ defaults,
845
+ ...candidate.multiple === true ? {
846
+ multiple: true
847
+ } : {},
848
+ ...candidate.allowFreeText === true ? {
849
+ allowFreeText: true
850
+ } : {}
531
851
  };
532
852
  }
533
- __name(resolveToolTransientRetryNumbers, "resolveToolTransientRetryNumbers");
534
- function delay(ms) {
535
- return new Promise((resolve) => {
536
- setTimeout(resolve, ms);
537
- });
538
- }
539
- __name(delay, "delay");
540
- async function invokeWithTransientRetry(fn, setting, options) {
541
- if (setting === false) {
542
- return fn();
543
- }
544
- const attempts = setting.attempts ?? DEFAULT_TOOL_TRANSIENT_RETRY_ATTEMPTS;
545
- const backoffMs = setting.backoffMs ?? DEFAULT_TOOL_TRANSIENT_RETRY_BACKOFF_MS;
546
- const classify = setting.classify ?? isTransientToolError;
547
- let attempt = 1;
548
- for (; ; ) {
549
- try {
550
- return await fn();
551
- } catch (error) {
552
- if (options?.isControlFlowError?.(error) === true) {
553
- throw error;
853
+ __name(parseQuestion, "parseQuestion");
854
+ var ASK_JSON_SCHEMA = {
855
+ type: "object",
856
+ additionalProperties: false,
857
+ properties: {
858
+ preamble: {
859
+ type: "string",
860
+ description: 'One sentence shown above the form, e.g. "Three questions before I start. I have pre-picked what I would choose, so confirming is enough."'
861
+ },
862
+ questions: {
863
+ type: "array",
864
+ minItems: 1,
865
+ maxItems: MAX_ASK_QUESTIONS,
866
+ items: {
867
+ type: "object",
868
+ additionalProperties: false,
869
+ required: [
870
+ "id",
871
+ "prompt",
872
+ "options",
873
+ "defaults"
874
+ ],
875
+ properties: {
876
+ id: {
877
+ type: "string",
878
+ description: "Unique within this call; answers come back under it."
879
+ },
880
+ prompt: {
881
+ type: "string"
882
+ },
883
+ multiple: {
884
+ type: "boolean",
885
+ description: "Allow more than one option to be chosen."
886
+ },
887
+ allowFreeText: {
888
+ type: "boolean",
889
+ description: "Accept an answer that is not an option."
890
+ },
891
+ defaults: {
892
+ type: "array",
893
+ minItems: 1,
894
+ items: {
895
+ type: "string"
896
+ },
897
+ description: "REQUIRED. The option values you would pick yourself, so the user can confirm rather than decide."
898
+ },
899
+ options: {
900
+ type: "array",
901
+ minItems: 2,
902
+ items: {
903
+ type: "object",
904
+ additionalProperties: false,
905
+ required: [
906
+ "value",
907
+ "label"
908
+ ],
909
+ properties: {
910
+ value: {
911
+ type: "string"
912
+ },
913
+ label: {
914
+ type: "string"
915
+ },
916
+ hotkey: {
917
+ type: "string",
918
+ maxLength: 1
919
+ }
920
+ }
921
+ }
922
+ }
923
+ }
554
924
  }
555
- const attemptsRemain = attempt < attempts;
556
- if (!attemptsRemain || !classify(error)) {
557
- throw error;
925
+ }
926
+ },
927
+ required: [
928
+ "questions"
929
+ ]
930
+ };
931
+ var askInputSchema = {
932
+ "~standard": {
933
+ version: 1,
934
+ vendor: "nestjs-agent",
935
+ validate: /* @__PURE__ */ __name((value) => {
936
+ const issues = [];
937
+ if (typeof value !== "object" || value === null) {
938
+ return {
939
+ issues: [
940
+ issue([], "must be an object")
941
+ ]
942
+ };
558
943
  }
559
- options?.onRetry?.(attempt, error);
560
- await delay(backoffMs * attempt);
561
- attempt += 1;
944
+ const candidate = value;
945
+ if (!Array.isArray(candidate.questions) || candidate.questions.length === 0) {
946
+ return {
947
+ issues: [
948
+ issue([
949
+ "questions"
950
+ ], "must be a non-empty array")
951
+ ]
952
+ };
953
+ }
954
+ if (candidate.questions.length > MAX_ASK_QUESTIONS) {
955
+ return {
956
+ issues: [
957
+ issue([
958
+ "questions"
959
+ ], `must hold at most ${MAX_ASK_QUESTIONS} questions`)
960
+ ]
961
+ };
962
+ }
963
+ const questions = [];
964
+ for (const [index, raw] of candidate.questions.entries()) {
965
+ const question = parseQuestion(raw, [
966
+ "questions",
967
+ index
968
+ ], issues);
969
+ if (question !== void 0) {
970
+ questions.push(question);
971
+ }
972
+ }
973
+ if (issues.length > 0) {
974
+ return {
975
+ issues
976
+ };
977
+ }
978
+ return {
979
+ value: {
980
+ questions,
981
+ ...typeof candidate.preamble === "string" ? {
982
+ preamble: candidate.preamble
983
+ } : {}
984
+ }
985
+ };
986
+ }, "validate"),
987
+ jsonSchema: {
988
+ input: /* @__PURE__ */ __name(() => ASK_JSON_SCHEMA, "input")
562
989
  }
563
990
  }
991
+ };
992
+ var ASK_TOOL_DESCRIPTION = "Ask the user to settle the scope of the work before you do it. Use it when a reasonable person would produce a materially different result depending on the answer \u2014 not to confirm something the conversation already says. Every question must pre-pick the answer you would choose, so the user can confirm instead of deciding. The user may decline, in which case you proceed on those pre-picked answers.";
993
+ function askToolDefinition() {
994
+ return {
995
+ name: ASK_TOOL_NAME,
996
+ kind: "ask",
997
+ description: ASK_TOOL_DESCRIPTION,
998
+ inputSchema: askInputSchema
999
+ };
564
1000
  }
565
- __name(invokeWithTransientRetry, "invokeWithTransientRetry");
1001
+ __name(askToolDefinition, "askToolDefinition");
1002
+ var DEFAULT_INTAKE_PREAMBLE = "A few questions before I start. I have pre-picked what I would choose, so confirming is enough.";
566
1003
 
567
- // src/agent-loop.ts
568
- function resolveCostUsd(usage, reportedCostUsd, price) {
569
- if (reportedCostUsd !== void 0) {
570
- return reportedCostUsd;
571
- }
572
- return price === void 0 ? null : estimateCost(usage, price);
1004
+ // src/skills.ts
1005
+ var GLOBAL_SCOPE = "global";
1006
+ function actorScope(actor) {
1007
+ return `actor:${actor.id}`;
573
1008
  }
574
- __name(resolveCostUsd, "resolveCostUsd");
575
- function buildContextBlock(passages) {
576
- const items = passages.map((passage, index) => {
577
- const label = passage.source !== void 0 ? ` (${passage.source})` : "";
578
- return `[${index + 1}]${label} ${passage.text}`;
579
- }).join("\n\n");
580
- return `<retrieved_context>
581
- ${items}
582
- </retrieved_context>
583
- Use the retrieved context above to answer when relevant, and cite sources by their bracket number.`;
1009
+ __name(actorScope, "actorScope");
1010
+ function tenantScope(tenantRef) {
1011
+ return `tenant:${tenantRef}`;
584
1012
  }
585
- __name(buildContextBlock, "buildContextBlock");
586
- var QuotaExceededError = class extends Error {
587
- static {
588
- __name(this, "QuotaExceededError");
589
- }
590
- constructor() {
591
- super("Daily token quota exceeded");
592
- this.name = "QuotaExceededError";
593
- }
1013
+ __name(tenantScope, "tenantScope");
1014
+ var defaultScopeResolver = {
1015
+ resolve: /* @__PURE__ */ __name(({ actor }) => [
1016
+ actorScope(actor),
1017
+ ...actor.tenantRef !== void 0 ? [
1018
+ tenantScope(actor.tenantRef)
1019
+ ] : [],
1020
+ GLOBAL_SCOPE
1021
+ ], "resolve")
594
1022
  };
595
- var MAX_DELEGATION_DEPTH = 5;
596
- async function resolvePrompt(prompt, ctx) {
597
- return typeof prompt === "function" ? prompt(ctx) : prompt;
1023
+ function staticSkillProvider(skills) {
1024
+ return {
1025
+ list: /* @__PURE__ */ __name(({ scopes }) => skills.filter((skill) => scopes.includes(skill.scope)), "list"),
1026
+ load: /* @__PURE__ */ __name(({ name, scope }) => skills.find((skill) => skill.name === name && skill.scope === scope)?.body ?? null, "load")
1027
+ };
598
1028
  }
599
- __name(resolvePrompt, "resolvePrompt");
600
- async function resolveSystemPrompt(deps, input) {
601
- const ctx = {
602
- actor: input.actor,
603
- agentName: input.agentName ?? "default",
604
- ...input.pageContext !== void 0 ? {
605
- pageContext: input.pageContext
606
- } : {}
1029
+ __name(staticSkillProvider, "staticSkillProvider");
1030
+ function compositeSkillProvider(providers) {
1031
+ return {
1032
+ list: /* @__PURE__ */ __name(async ({ scopes, ctx }) => {
1033
+ const seen = /* @__PURE__ */ new Set();
1034
+ const merged = [];
1035
+ for (const provider of providers) {
1036
+ for (const summary of await provider.list({
1037
+ scopes,
1038
+ ctx
1039
+ })) {
1040
+ const key = `${summary.scope}\0${summary.name}`;
1041
+ if (!seen.has(key)) {
1042
+ seen.add(key);
1043
+ merged.push(summary);
1044
+ }
1045
+ }
1046
+ }
1047
+ return merged;
1048
+ }, "list"),
1049
+ load: /* @__PURE__ */ __name(async ({ name, scope, ctx }) => {
1050
+ for (const provider of providers) {
1051
+ const body = await provider.load({
1052
+ name,
1053
+ scope,
1054
+ ctx
1055
+ });
1056
+ if (body !== null) {
1057
+ return body;
1058
+ }
1059
+ }
1060
+ return null;
1061
+ }, "load")
607
1062
  };
608
- const sections = [
609
- await resolvePrompt(deps.systemPrompt, ctx)
610
- ];
611
- for (const contribute of deps.promptContributors ?? []) {
612
- const section = await contribute(ctx);
613
- if (section !== null && section.length > 0) {
614
- sections.push(section);
1063
+ }
1064
+ __name(compositeSkillProvider, "compositeSkillProvider");
1065
+ var DEFAULT_MAX_SKILLS = 20;
1066
+ function resolveSkillCatalog(summaries, scopes, maxSkills = DEFAULT_MAX_SKILLS) {
1067
+ const rank = new Map(scopes.map((scope, index) => [
1068
+ scope,
1069
+ index
1070
+ ]));
1071
+ const byName = /* @__PURE__ */ new Map();
1072
+ for (const summary of summaries) {
1073
+ if (!rank.has(summary.scope)) {
1074
+ continue;
1075
+ }
1076
+ const existing = byName.get(summary.name);
1077
+ if (existing === void 0) {
1078
+ byName.set(summary.name, [
1079
+ summary
1080
+ ]);
1081
+ } else {
1082
+ existing.push(summary);
615
1083
  }
616
1084
  }
617
- return sections.join("\n\n");
618
- }
619
- __name(resolveSystemPrompt, "resolveSystemPrompt");
620
- function extractTask(input) {
621
- if (typeof input === "object" && input !== null && "task" in input) {
622
- const task = input.task;
623
- if (typeof task === "string") {
624
- return task;
1085
+ const resolved = [];
1086
+ for (const candidates of byName.values()) {
1087
+ const ordered = [
1088
+ ...candidates
1089
+ ].sort((a, b) => (rank.get(a.scope) ?? 0) - (rank.get(b.scope) ?? 0));
1090
+ const winner = ordered[0];
1091
+ if (winner === void 0) {
1092
+ continue;
625
1093
  }
1094
+ const shadows = ordered.slice(1).map((candidate) => candidate.scope);
1095
+ resolved.push({
1096
+ name: winner.name,
1097
+ description: winner.description,
1098
+ scope: winner.scope,
1099
+ ...shadows.length > 0 ? {
1100
+ shadows
1101
+ } : {}
1102
+ });
626
1103
  }
627
- return JSON.stringify(input);
1104
+ resolved.sort((a, b) => {
1105
+ const byScope = (rank.get(a.scope) ?? 0) - (rank.get(b.scope) ?? 0);
1106
+ return byScope !== 0 ? byScope : compare(a.name, b.name);
1107
+ });
1108
+ return {
1109
+ entries: resolved.slice(0, maxSkills),
1110
+ omitted: Math.max(0, resolved.length - maxSkills)
1111
+ };
628
1112
  }
629
- __name(extractTask, "extractTask");
630
- function deriveTitle(userText) {
631
- const trimmed = userText.trim().replace(/\s+/g, " ");
632
- return trimmed.length > 60 ? `${trimmed.slice(0, 57)}...` : trimmed || "New chat";
1113
+ __name(resolveSkillCatalog, "resolveSkillCatalog");
1114
+ function compare(a, b) {
1115
+ return a < b ? -1 : a > b ? 1 : 0;
633
1116
  }
634
- __name(deriveTitle, "deriveTitle");
635
- var ToolTimeoutError = class ToolTimeoutError2 extends Error {
636
- static {
637
- __name(this, "ToolTimeoutError");
638
- }
639
- constructor(toolName, ms) {
640
- super(`Tool "${toolName}" exceeded its ${ms}ms timeout`);
641
- this.name = "ToolTimeoutError";
1117
+ __name(compare, "compare");
1118
+ async function offerSkills(config, ctx) {
1119
+ const scopes = await (config.scopes ?? defaultScopeResolver).resolve(ctx);
1120
+ const summaries = await config.provider.list({
1121
+ scopes,
1122
+ ctx
1123
+ });
1124
+ return {
1125
+ scopes,
1126
+ ...resolveSkillCatalog(summaries, scopes, config.maxSkills)
1127
+ };
1128
+ }
1129
+ __name(offerSkills, "offerSkills");
1130
+ function buildSkillsBlock(entries) {
1131
+ const lines = entries.map((entry) => {
1132
+ const shadows = entry.shadows === void 0 ? "" : ` (overrides the one from ${entry.shadows.join(", ")})`;
1133
+ return `- ${entry.name} [${entry.scope}] \u2014 ${entry.description}${shadows}`;
1134
+ });
1135
+ return `<skills>
1136
+ Procedures available to you. Each line is a name, the scope it came from, and what it covers \u2014 not the procedure itself.
1137
+ Call the \`${SKILL_TOOL_NAME}\` tool with a name from this list to read one BEFORE doing the task it covers, and follow what it says; never guess its contents from the description.
1138
+ A skill from a narrower scope overrides a same-named one from a wider scope. When you follow one that overrides another, say which scope it came from, so the user knows their setting differs from the wider default.
1139
+ ${lines.join("\n")}
1140
+ </skills>`;
1141
+ }
1142
+ __name(buildSkillsBlock, "buildSkillsBlock");
1143
+ var SKILL_TOOL_NAME = "skill";
1144
+ var SKILL_JSON_SCHEMA = {
1145
+ type: "object",
1146
+ additionalProperties: false,
1147
+ required: [
1148
+ "name"
1149
+ ],
1150
+ properties: {
1151
+ name: {
1152
+ type: "string",
1153
+ description: "The name of a skill from the <skills> list, exactly as written there."
1154
+ }
642
1155
  }
643
1156
  };
644
- function withToolTimeout(work, ms, toolName) {
645
- return new Promise((resolve, reject) => {
646
- const timer = setTimeout(() => reject(new ToolTimeoutError(toolName, ms)), ms);
647
- work.then((value) => {
648
- clearTimeout(timer);
649
- resolve(value);
650
- }, (error) => {
651
- clearTimeout(timer);
652
- reject(error);
653
- });
654
- });
1157
+ function issue2(path, message) {
1158
+ return {
1159
+ message,
1160
+ path
1161
+ };
655
1162
  }
656
- __name(withToolTimeout, "withToolTimeout");
657
- function parseFollowUps(text, count) {
658
- const source = text.match(/\[[\s\S]*\]/)?.[0] ?? text;
659
- try {
660
- const parsed = JSON.parse(source);
661
- if (Array.isArray(parsed)) {
662
- return parsed.filter((item) => typeof item === "string").slice(0, count);
1163
+ __name(issue2, "issue");
1164
+ var skillInputSchema = {
1165
+ "~standard": {
1166
+ version: 1,
1167
+ vendor: "nestjs-agent",
1168
+ validate: /* @__PURE__ */ __name((value) => {
1169
+ if (typeof value !== "object" || value === null) {
1170
+ return {
1171
+ issues: [
1172
+ issue2([], "must be an object")
1173
+ ]
1174
+ };
1175
+ }
1176
+ const candidate = value;
1177
+ if (typeof candidate.name !== "string" || candidate.name.length === 0) {
1178
+ return {
1179
+ issues: [
1180
+ issue2([
1181
+ "name"
1182
+ ], "must be a non-empty string")
1183
+ ]
1184
+ };
1185
+ }
1186
+ return {
1187
+ value: {
1188
+ name: candidate.name
1189
+ }
1190
+ };
1191
+ }, "validate"),
1192
+ jsonSchema: {
1193
+ input: /* @__PURE__ */ __name(() => SKILL_JSON_SCHEMA, "input")
663
1194
  }
664
- } catch {
665
1195
  }
666
- return [];
1196
+ };
1197
+ var SKILL_TOOL_DESCRIPTION = "Read one of the procedures listed in <skills>. Call it before doing a task a listed skill covers, and follow what it returns. It performs nothing and changes nothing \u2014 it only gives you instructions you do not yet have.";
1198
+ function skillToolDefinition() {
1199
+ return {
1200
+ name: SKILL_TOOL_NAME,
1201
+ kind: "skill",
1202
+ description: SKILL_TOOL_DESCRIPTION,
1203
+ inputSchema: skillInputSchema
1204
+ };
667
1205
  }
668
- __name(parseFollowUps, "parseFollowUps");
669
- async function generateFollowUps(model, messages, count) {
670
- const discard = {
671
- write: /* @__PURE__ */ __name(() => {
672
- }, "write"),
673
- end: /* @__PURE__ */ __name(() => {
1206
+ __name(skillToolDefinition, "skillToolDefinition");
1207
+ function withSkillTool({ tools, enabled }) {
1208
+ return enabled ? [
1209
+ ...tools,
1210
+ skillToolDefinition()
1211
+ ] : tools;
1212
+ }
1213
+ __name(withSkillTool, "withSkillTool");
1214
+ async function loadSkill(config, offer, name, ctx) {
1215
+ const entry = offer.entries.find((candidate) => candidate.name === name);
1216
+ if (entry === void 0) {
1217
+ const available = offer.entries.map((candidate) => candidate.name).join(", ");
1218
+ return {
1219
+ ok: false,
1220
+ error: available.length === 0 ? `No skill named "${name}" is available to you, and no others are either.` : `No skill named "${name}" is available to you. Available: ${available}.`
1221
+ };
1222
+ }
1223
+ const body = await config.provider.load({
1224
+ name: entry.name,
1225
+ scope: entry.scope,
1226
+ ctx
1227
+ });
1228
+ if (body === null) {
1229
+ return {
1230
+ ok: false,
1231
+ error: `Skill "${name}" is listed but its body could not be read.`
1232
+ };
1233
+ }
1234
+ return {
1235
+ ok: true,
1236
+ skill: {
1237
+ name: entry.name,
1238
+ description: entry.description,
1239
+ scope: entry.scope,
1240
+ body
1241
+ },
1242
+ ...entry.shadows !== void 0 ? {
1243
+ shadows: entry.shadows
1244
+ } : {}
1245
+ };
1246
+ }
1247
+ __name(loadSkill, "loadSkill");
1248
+ function skillWriteVerdict(request) {
1249
+ const { scope, scopes, author, actor } = request;
1250
+ if (!scopes.includes(scope)) {
1251
+ return {
1252
+ allowed: false,
1253
+ reason: `"${scope}" is not a scope this actor belongs to`
1254
+ };
1255
+ }
1256
+ const own = actorScope(actor);
1257
+ if (scope === own) {
1258
+ return {
1259
+ allowed: true
1260
+ };
1261
+ }
1262
+ if (author.kind !== "human") {
1263
+ return {
1264
+ allowed: false,
1265
+ reason: `only a human may author a skill at "${scope}"; an agent may write only at "${own}"`
1266
+ };
1267
+ }
1268
+ return request.elevated === true ? {
1269
+ allowed: true
1270
+ } : {
1271
+ allowed: false,
1272
+ reason: `authoring at "${scope}" requires an elevated human author`
1273
+ };
1274
+ }
1275
+ __name(skillWriteVerdict, "skillWriteVerdict");
1276
+
1277
+ // src/memory.ts
1278
+ var DEFAULT_MAX_MEMORIES = 20;
1279
+ var DEFAULT_MAX_FACT_CHARS = 240;
1280
+ function resolveMemoryDigest({ records, scopes, maxMemories = DEFAULT_MAX_MEMORIES, ranked = false }) {
1281
+ const rank = new Map(scopes.map((scope, index) => [
1282
+ scope,
1283
+ index
1284
+ ]));
1285
+ const byKey = /* @__PURE__ */ new Map();
1286
+ for (const entry of records) {
1287
+ if (!rank.has(entry.scope)) {
1288
+ continue;
1289
+ }
1290
+ const existing = byKey.get(entry.key);
1291
+ if (existing === void 0) {
1292
+ byKey.set(entry.key, [
1293
+ entry
1294
+ ]);
1295
+ } else {
1296
+ existing.push(entry);
1297
+ }
1298
+ }
1299
+ const resolved = [];
1300
+ for (const candidates of byKey.values()) {
1301
+ const ordered = [
1302
+ ...candidates
1303
+ ].sort((a, b) => (rank.get(a.scope) ?? 0) - (rank.get(b.scope) ?? 0));
1304
+ const winner = ordered[0];
1305
+ if (winner === void 0) {
1306
+ continue;
1307
+ }
1308
+ const overrides = ordered.slice(1).map((beaten) => ({
1309
+ scope: beaten.scope,
1310
+ text: beaten.text,
1311
+ author: beaten.origin.author
1312
+ }));
1313
+ const pinned = ordered.some((candidate) => candidate.pinned === true);
1314
+ resolved.push({
1315
+ ...winner,
1316
+ ...pinned ? {
1317
+ pinned: true
1318
+ } : {},
1319
+ ...overrides.length > 0 ? {
1320
+ overrides
1321
+ } : {}
1322
+ });
1323
+ }
1324
+ const byPrecedence = /* @__PURE__ */ __name((a, b) => (rank.get(a.scope) ?? 0) - (rank.get(b.scope) ?? 0) || compare2(b.updatedAt, a.updatedAt) || compare2(a.key, b.key), "byPrecedence");
1325
+ const selected = resolved.map((entry, index) => ({
1326
+ entry,
1327
+ index
1328
+ })).sort((a, b) => pinRank(a.entry) - pinRank(b.entry) || (ranked && a.entry.pinned !== true ? a.index - b.index : byPrecedence(a.entry, b.entry))).slice(0, maxMemories).map((candidate) => candidate.entry);
1329
+ const pinnedOmitted = resolved.filter((entry) => entry.pinned === true).length - selected.filter((entry) => entry.pinned === true).length;
1330
+ selected.sort((a, b) => pinRank(a) - pinRank(b) || (rank.get(a.scope) ?? 0) - (rank.get(b.scope) ?? 0) || compare2(a.key, b.key));
1331
+ return {
1332
+ entries: selected,
1333
+ omitted: Math.max(0, resolved.length - maxMemories),
1334
+ pinnedOmitted
1335
+ };
1336
+ }
1337
+ __name(resolveMemoryDigest, "resolveMemoryDigest");
1338
+ function pinRank(entry) {
1339
+ return entry.pinned === true ? 0 : 1;
1340
+ }
1341
+ __name(pinRank, "pinRank");
1342
+ function compare2(a, b) {
1343
+ return a < b ? -1 : a > b ? 1 : 0;
1344
+ }
1345
+ __name(compare2, "compare");
1346
+ async function offerMemories({ config, ctx, query }) {
1347
+ const scopes = await (config.scopes ?? defaultScopeResolver).resolve(ctx);
1348
+ const maxMemories = config.maxMemories ?? DEFAULT_MAX_MEMORIES;
1349
+ const trimmed = query?.trim() ?? "";
1350
+ const search = trimmed.length > 0 ? config.provider.search : void 0;
1351
+ const records = search !== void 0 ? await search({
1352
+ scopes,
1353
+ query: trimmed,
1354
+ limit: maxMemories,
1355
+ ctx
1356
+ }) : await config.provider.list({
1357
+ scopes,
1358
+ ctx
1359
+ });
1360
+ return {
1361
+ scopes,
1362
+ recalled: search !== void 0,
1363
+ ...resolveMemoryDigest({
1364
+ records,
1365
+ scopes,
1366
+ maxMemories,
1367
+ ranked: search !== void 0
1368
+ })
1369
+ };
1370
+ }
1371
+ __name(offerMemories, "offerMemories");
1372
+ var ASSERTED_FRAMING = "Stated by people. A person wrote each of these deliberately \u2014 the user about themselves, or someone administering their organisation. Treat them as you would any other instruction you were given. If the user says something that contradicts one, say which note it conflicts with and at what scope it is held, rather than quietly setting it aside.";
1373
+ var CONCLUDED_FRAMING = "Concluded by you. These are your own notes from earlier turns, not documents anyone wrote: they may be wrong or out of date, so prefer what the user says now, and say where a note came from if you act on it.";
1374
+ var PARTIAL_FRAMING = "You are being shown a selection, not everything on file. Other notes exist that are not in this list, so never read something\u2019s absence from it as evidence that it was never recorded.";
1375
+ function memorySection(framing, entries) {
1376
+ if (entries.length === 0) {
1377
+ return [];
1378
+ }
1379
+ return [
1380
+ framing,
1381
+ ...entries.flatMap((entry) => [
1382
+ `- [${entry.scope}] ${entry.key}: ${entry.text}`,
1383
+ ...(entry.overrides ?? []).map((beaten) => ` \u21B3 [${beaten.scope}] ${beaten.author === "human" ? "a person stated" : "instead has"}: ${beaten.text}`)
1384
+ ])
1385
+ ];
1386
+ }
1387
+ __name(memorySection, "memorySection");
1388
+ function buildMemoryBlock({ entries, writable, partial }) {
1389
+ const writeLine = writable ? [
1390
+ `When you learn something durable about this user, record it with the \`${REMEMBER_TOOL_NAME}\` tool; when one of these turns out to be wrong, record the corrected fact under the same key. What you record that way is one of your own notes, not something stated by a person.`
1391
+ ] : [];
1392
+ const body = [
1393
+ "What is on file about this user and their organisation, carried over from earlier conversations. Each line is a scope, a key and the fact. A narrower scope overrides a wider one; where the wider value is shown underneath, tell the user their setting differs from it rather than silently choosing one.",
1394
+ ...partial ? [
1395
+ PARTIAL_FRAMING
1396
+ ] : [],
1397
+ ...writeLine,
1398
+ // People before inferences: the model meets what it was told before what it worked out.
1399
+ ...memorySection(ASSERTED_FRAMING, entries.filter((entry) => entry.origin.author === "human")),
1400
+ ...memorySection(CONCLUDED_FRAMING, entries.filter((entry) => entry.origin.author !== "human"))
1401
+ ];
1402
+ return `<memory>
1403
+ ${body.join("\n")}
1404
+ </memory>`;
1405
+ }
1406
+ __name(buildMemoryBlock, "buildMemoryBlock");
1407
+ var REMEMBER_TOOL_NAME = "remember";
1408
+ var REMEMBER_JSON_SCHEMA = {
1409
+ type: "object",
1410
+ additionalProperties: false,
1411
+ required: [
1412
+ "key",
1413
+ "fact"
1414
+ ],
1415
+ properties: {
1416
+ key: {
1417
+ type: "string",
1418
+ description: 'A short, stable handle for what this fact is ABOUT (e.g. "fiscal-year", "preferred-units"). Reusing an existing key replaces that fact rather than adding a second one.'
1419
+ },
1420
+ fact: {
1421
+ type: "string",
1422
+ description: "The fact itself, in one sentence, stated so it still makes sense in a conversation months from now."
1423
+ }
1424
+ }
1425
+ };
1426
+ function issue3(path, message) {
1427
+ return {
1428
+ message,
1429
+ path
1430
+ };
1431
+ }
1432
+ __name(issue3, "issue");
1433
+ var rememberInputSchema = {
1434
+ "~standard": {
1435
+ version: 1,
1436
+ vendor: "nestjs-agent",
1437
+ validate: /* @__PURE__ */ __name((value) => {
1438
+ if (typeof value !== "object" || value === null) {
1439
+ return {
1440
+ issues: [
1441
+ issue3([], "must be an object")
1442
+ ]
1443
+ };
1444
+ }
1445
+ const candidate = value;
1446
+ if (typeof candidate.key !== "string" || candidate.key.length === 0) {
1447
+ return {
1448
+ issues: [
1449
+ issue3([
1450
+ "key"
1451
+ ], "must be a non-empty string")
1452
+ ]
1453
+ };
1454
+ }
1455
+ if (typeof candidate.fact !== "string" || candidate.fact.length === 0) {
1456
+ return {
1457
+ issues: [
1458
+ issue3([
1459
+ "fact"
1460
+ ], "must be a non-empty string")
1461
+ ]
1462
+ };
1463
+ }
1464
+ return {
1465
+ value: {
1466
+ key: candidate.key,
1467
+ fact: candidate.fact
1468
+ }
1469
+ };
1470
+ }, "validate"),
1471
+ jsonSchema: {
1472
+ input: /* @__PURE__ */ __name(() => REMEMBER_JSON_SCHEMA, "input")
1473
+ }
1474
+ }
1475
+ };
1476
+ var REMEMBER_TOOL_DESCRIPTION = "Record one durable fact about this user under a key, so later conversations start knowing it. For things that will still be true another day \u2014 how they work, what their organisation requires, a correction they made. Not for what this conversation is about, and not for anything you were not told or could not reasonably infer. The user can read and delete everything you record here.";
1477
+ function rememberToolDefinition() {
1478
+ return {
1479
+ name: REMEMBER_TOOL_NAME,
1480
+ kind: "memory",
1481
+ description: REMEMBER_TOOL_DESCRIPTION,
1482
+ inputSchema: rememberInputSchema
1483
+ };
1484
+ }
1485
+ __name(rememberToolDefinition, "rememberToolDefinition");
1486
+ function withMemoryTool({ tools, enabled }) {
1487
+ return enabled ? [
1488
+ ...tools,
1489
+ rememberToolDefinition()
1490
+ ] : tools;
1491
+ }
1492
+ __name(withMemoryTool, "withMemoryTool");
1493
+ function memoryWriteVerdict(request) {
1494
+ const { scope, scopes, author, actor } = request;
1495
+ if (!scopes.includes(scope)) {
1496
+ return {
1497
+ allowed: false,
1498
+ reason: `"${scope}" is not a scope this actor belongs to`
1499
+ };
1500
+ }
1501
+ const own = actorScope(actor);
1502
+ if (scope === own) {
1503
+ return {
1504
+ allowed: true
1505
+ };
1506
+ }
1507
+ if (author.kind !== "human") {
1508
+ return {
1509
+ allowed: false,
1510
+ reason: `only a human may write a memory at "${scope}"; an agent may write only at "${own}"`
1511
+ };
1512
+ }
1513
+ return request.elevated === true ? {
1514
+ allowed: true
1515
+ } : {
1516
+ allowed: false,
1517
+ reason: `writing at "${scope}" requires an elevated human author`
1518
+ };
1519
+ }
1520
+ __name(memoryWriteVerdict, "memoryWriteVerdict");
1521
+ function memoryForgetVerdict({ record, actor }) {
1522
+ return record.scope === actorScope(actor) ? {
1523
+ allowed: true
1524
+ } : {
1525
+ allowed: false,
1526
+ reason: `"${record.scope}" is not this actor's own scope; deleting it is an administrative action`
1527
+ };
1528
+ }
1529
+ __name(memoryForgetVerdict, "memoryForgetVerdict");
1530
+ async function writeMemory({ config, digest, call, ctx, runId }) {
1531
+ const write = config.provider.write;
1532
+ if (write === void 0) {
1533
+ return {
1534
+ ok: false,
1535
+ error: "Memory is read-only in this deployment."
1536
+ };
1537
+ }
1538
+ const key = call.key.trim();
1539
+ const text = call.fact.trim();
1540
+ if (key.length === 0 || text.length === 0) {
1541
+ return {
1542
+ ok: false,
1543
+ error: "A memory needs both a key and a fact."
1544
+ };
1545
+ }
1546
+ const maxFactChars = config.maxFactChars ?? DEFAULT_MAX_FACT_CHARS;
1547
+ if (text.length > maxFactChars) {
1548
+ return {
1549
+ ok: false,
1550
+ error: `A memory must be at most ${maxFactChars} characters; that was ${text.length}. State the fact more briefly.`
1551
+ };
1552
+ }
1553
+ const scope = actorScope(ctx.actor);
1554
+ const verdict = memoryWriteVerdict({
1555
+ scope,
1556
+ actor: ctx.actor,
1557
+ scopes: digest.scopes,
1558
+ author: {
1559
+ kind: "agent",
1560
+ actorRef: ctx.actor.id
1561
+ }
1562
+ });
1563
+ if (!verdict.allowed) {
1564
+ return {
1565
+ ok: false,
1566
+ error: verdict.reason
1567
+ };
1568
+ }
1569
+ const record = await write({
1570
+ key,
1571
+ text,
1572
+ scope,
1573
+ origin: {
1574
+ author: "agent",
1575
+ threadId: ctx.threadId,
1576
+ runId,
1577
+ actorRef: ctx.actor.id
1578
+ },
1579
+ ctx
1580
+ });
1581
+ return {
1582
+ ok: true,
1583
+ record
1584
+ };
1585
+ }
1586
+ __name(writeMemory, "writeMemory");
1587
+
1588
+ // src/agent-registry.ts
1589
+ var AgentRegistry = class {
1590
+ static {
1591
+ __name(this, "AgentRegistry");
1592
+ }
1593
+ definitions = /* @__PURE__ */ new Map();
1594
+ register(definition) {
1595
+ this.definitions.set(definition.name, definition);
1596
+ }
1597
+ get(name) {
1598
+ return this.definitions.get(name);
1599
+ }
1600
+ has(name) {
1601
+ return this.definitions.has(name);
1602
+ }
1603
+ list() {
1604
+ return [
1605
+ ...this.definitions.values()
1606
+ ];
1607
+ }
1608
+ };
1609
+
1610
+ // src/replay-integrity.ts
1611
+ function isReplayIntegrityError(error) {
1612
+ return error instanceof Error && error.name.toLowerCase().endsWith("nondeterminismerror");
1613
+ }
1614
+ __name(isReplayIntegrityError, "isReplayIntegrityError");
1615
+
1616
+ // src/control-flow.ts
1617
+ var CONTROL_FLOW_SIGNAL = Symbol.for("aviary:durable:control-flow");
1618
+ function isControlFlowSignal(error) {
1619
+ return typeof error === "object" && error !== null && error[CONTROL_FLOW_SIGNAL] === true;
1620
+ }
1621
+ __name(isControlFlowSignal, "isControlFlowSignal");
1622
+
1623
+ // src/delegation.ts
1624
+ function normalizeDelegation(entry) {
1625
+ return typeof entry === "string" ? {
1626
+ agent: entry,
1627
+ detached: false
1628
+ } : {
1629
+ agent: entry.agent,
1630
+ detached: entry.detached === true
1631
+ };
1632
+ }
1633
+ __name(normalizeDelegation, "normalizeDelegation");
1634
+ function detachedStarted(args) {
1635
+ return {
1636
+ detached: true,
1637
+ status: "started",
1638
+ agent: args.agent,
1639
+ runId: args.runId,
1640
+ note: `The "${args.agent}" agent is now working on this in the background. Its answer is NOT part of this turn and will arrive in this conversation as a separate message when it is ready. Tell the user the work has started; do not state or guess what it will find.`
1641
+ };
1642
+ }
1643
+ __name(detachedStarted, "detachedStarted");
1644
+ function detachedDelivered(args) {
1645
+ return {
1646
+ detached: true,
1647
+ status: "delivered",
1648
+ agent: args.agent,
1649
+ runId: args.runId,
1650
+ text: args.text
1651
+ };
1652
+ }
1653
+ __name(detachedDelivered, "detachedDelivered");
1654
+ function detachedUnsettled(args) {
1655
+ return {
1656
+ detached: true,
1657
+ status: args.status,
1658
+ agent: args.agent,
1659
+ runId: args.runId,
1660
+ ...args.error !== void 0 ? {
1661
+ error: args.error
1662
+ } : {}
1663
+ };
1664
+ }
1665
+ __name(detachedUnsettled, "detachedUnsettled");
1666
+ async function settleUnsettledDelegation(args) {
1667
+ const { store, delivery, agent, runId, status } = args;
1668
+ if (await store.getThread(delivery.threadId) === null) {
1669
+ return;
1670
+ }
1671
+ await store.appendMessage({
1672
+ threadId: delivery.threadId,
1673
+ role: "assistant",
1674
+ content: status === "cancelled" ? `The "${agent}" agent was stopped before it could answer.` : `The "${agent}" agent stopped before it could answer: ${args.error ?? "unknown error"}`,
1675
+ agentName: agent,
1676
+ runId
1677
+ });
1678
+ await store.updateToolCall({
1679
+ toolCallId: delivery.toolCallId,
1680
+ status: "executed",
1681
+ output: detachedUnsettled({
1682
+ agent,
1683
+ runId,
1684
+ status,
1685
+ ...args.error !== void 0 ? {
1686
+ error: args.error
1687
+ } : {}
1688
+ })
1689
+ });
1690
+ }
1691
+ __name(settleUnsettledDelegation, "settleUnsettledDelegation");
1692
+
1693
+ // src/tool-registry.ts
1694
+ var ToolForbiddenError = class extends Error {
1695
+ static {
1696
+ __name(this, "ToolForbiddenError");
1697
+ }
1698
+ toolName;
1699
+ constructor(toolName) {
1700
+ super(`Tool "${toolName}" is not allowed for this role`), this.toolName = toolName;
1701
+ this.name = "ToolForbiddenError";
1702
+ }
1703
+ };
1704
+ var ToolDisabledError = class extends Error {
1705
+ static {
1706
+ __name(this, "ToolDisabledError");
1707
+ }
1708
+ toolName;
1709
+ constructor(toolName) {
1710
+ super(`Tool "${toolName}" is disabled in this deployment`), this.toolName = toolName;
1711
+ this.name = "ToolDisabledError";
1712
+ }
1713
+ };
1714
+ var ToolNotFoundError = class extends Error {
1715
+ static {
1716
+ __name(this, "ToolNotFoundError");
1717
+ }
1718
+ toolName;
1719
+ constructor(toolName) {
1720
+ super(`Tool "${toolName}" is not registered`), this.toolName = toolName;
1721
+ this.name = "ToolNotFoundError";
1722
+ }
1723
+ };
1724
+ var ToolInputInvalidError = class extends Error {
1725
+ static {
1726
+ __name(this, "ToolInputInvalidError");
1727
+ }
1728
+ toolName;
1729
+ issues;
1730
+ constructor(toolName, issues) {
1731
+ super(`Invalid input for tool "${toolName}": ${issues.map((issue4) => issue4.message).join("; ")}`), this.toolName = toolName, this.issues = issues;
1732
+ this.name = "ToolInputInvalidError";
1733
+ }
1734
+ };
1735
+ var ToolRegistry = class {
1736
+ static {
1737
+ __name(this, "ToolRegistry");
1738
+ }
1739
+ entries = /* @__PURE__ */ new Map();
1740
+ register(spec, handler) {
1741
+ this.entries.set(spec.name, {
1742
+ spec,
1743
+ handler
1744
+ });
1745
+ }
1746
+ has(name) {
1747
+ return this.entries.has(name);
1748
+ }
1749
+ spec(name) {
1750
+ return this.entries.get(name)?.spec;
1751
+ }
1752
+ allSpecs() {
1753
+ return [
1754
+ ...this.entries.values()
1755
+ ].map((entry) => entry.spec);
1756
+ }
1757
+ /**
1758
+ * The tools to offer the model for this actor+agent, after the four filter layers: what this
1759
+ * agent pinned, what this deployment has enabled, what this actor's role allows, and what each
1760
+ * tool's own `canUse` allows this actor.
1761
+ *
1762
+ * Every layer only ever removes tools, so no arrangement of them can widen what a turn reaches.
1763
+ * The agent's allow-list therefore goes FIRST, even though it is the narrowest statement: it is a
1764
+ * pure name-set match, while each of the three below it may be a round trip — an authz service, a
1765
+ * feature-flag store, an MCP server — and this runs once per model step. Asking those about a
1766
+ * tool the allow-list has already excluded is a call whose answer nothing reads.
1767
+ */
1768
+ async definitionsFor(actor, policy, allowedTools) {
1769
+ const pinnedNames = new Set(filterToolsByAllowList(this.allSpecs(), allowedTools).map((spec) => spec.name));
1770
+ const pinned = [
1771
+ ...this.entries.values()
1772
+ ].filter((entry) => pinnedNames.has(entry.spec.name));
1773
+ const live = await filterToolsByEnabled(pinned);
1774
+ const allowedByRole = new Set((await filterToolsByRole(live.map((entry) => entry.spec), actor, policy)).map((spec) => spec.name));
1775
+ const roleScoped = live.filter((entry) => allowedByRole.has(entry.spec.name));
1776
+ const actorScoped = await filterToolsByCanUse(roleScoped, actor);
1777
+ return actorScoped.map(({ spec }) => ({
1778
+ name: spec.name,
1779
+ kind: spec.kind,
1780
+ description: spec.description,
1781
+ inputSchema: spec.inputSchema
1782
+ }));
1783
+ }
1784
+ /**
1785
+ * Run a tool. Re-checks that the tool is enabled and that the role allows it (defense-in-depth —
1786
+ * a call can reach here from a replayed durable step or an approval granted before the flag
1787
+ * moved, neither of which went through `definitionsFor` again) and re-parses the input via Zod.
1788
+ */
1789
+ async invoke(name, input, ctx, policy) {
1790
+ const entry = this.entries.get(name);
1791
+ if (entry === void 0) {
1792
+ throw new ToolNotFoundError(name);
1793
+ }
1794
+ if (!await isToolEnabled(entry.spec, entry.handler)) {
1795
+ throw new ToolDisabledError(name);
1796
+ }
1797
+ if (!await policy.can(ctx.actor, entry.spec)) {
1798
+ throw new ToolForbiddenError(name);
1799
+ }
1800
+ if (!await canActorUseTool(ctx.actor, entry.handler)) {
1801
+ throw new ToolForbiddenError(name);
1802
+ }
1803
+ const validation = await entry.spec.inputSchema["~standard"].validate(input);
1804
+ if (validation.issues !== void 0) {
1805
+ throw new ToolInputInvalidError(name, validation.issues);
1806
+ }
1807
+ return entry.handler.execute(validation.value, ctx);
1808
+ }
1809
+ };
1810
+ var DefaultRolesPolicy = class {
1811
+ static {
1812
+ __name(this, "DefaultRolesPolicy");
1813
+ }
1814
+ defaultRoles;
1815
+ constructor(defaultRoles = [
1816
+ "ADMIN"
1817
+ ]) {
1818
+ this.defaultRoles = defaultRoles;
1819
+ }
1820
+ can(actor, tool) {
1821
+ const allowed = tool.roles ?? this.defaultRoles;
1822
+ return (actor.roles ?? []).some((role) => allowed.includes(role));
1823
+ }
1824
+ };
1825
+
1826
+ // src/agent-loop.ts
1827
+ import { createHash } from "node:crypto";
1828
+ import { trace } from "@dudousxd/nestjs-diagnostics";
1829
+
1830
+ // src/diagnostics.ts
1831
+ import { emit } from "@dudousxd/nestjs-diagnostics";
1832
+ function publishAgentRunStarted(payload) {
1833
+ emit("agent", "run.started", payload);
1834
+ }
1835
+ __name(publishAgentRunStarted, "publishAgentRunStarted");
1836
+ function publishAgentMessage(payload) {
1837
+ emit("agent", "message", payload);
1838
+ }
1839
+ __name(publishAgentMessage, "publishAgentMessage");
1840
+ function publishAgentToolCall(payload) {
1841
+ emit("agent", "tool-call", payload);
1842
+ }
1843
+ __name(publishAgentToolCall, "publishAgentToolCall");
1844
+ function publishAgentQuotaExceeded(payload) {
1845
+ emit("agent", "quota.exceeded", payload);
1846
+ }
1847
+ __name(publishAgentQuotaExceeded, "publishAgentQuotaExceeded");
1848
+ function publishAgentRunFinished(payload) {
1849
+ emit("agent", "run.finished", payload);
1850
+ }
1851
+ __name(publishAgentRunFinished, "publishAgentRunFinished");
1852
+ function publishAgentRunFailed(payload) {
1853
+ emit("agent", "run.failed", payload);
1854
+ }
1855
+ __name(publishAgentRunFailed, "publishAgentRunFailed");
1856
+ function publishAgentDelegated(payload) {
1857
+ emit("agent", "delegated", payload);
1858
+ }
1859
+ __name(publishAgentDelegated, "publishAgentDelegated");
1860
+ function publishAgentRetrieved(payload) {
1861
+ emit("agent", "retrieved", payload);
1862
+ }
1863
+ __name(publishAgentRetrieved, "publishAgentRetrieved");
1864
+ function publishAgentToolRetry(payload) {
1865
+ emit("agent", "tool.retry", payload);
1866
+ }
1867
+ __name(publishAgentToolRetry, "publishAgentToolRetry");
1868
+ function publishAgentSkillsResolved(payload) {
1869
+ emit("agent", "skills.resolved", payload);
1870
+ }
1871
+ __name(publishAgentSkillsResolved, "publishAgentSkillsResolved");
1872
+ function publishAgentMemoryResolved(payload) {
1873
+ emit("agent", "memory.resolved", payload);
1874
+ }
1875
+ __name(publishAgentMemoryResolved, "publishAgentMemoryResolved");
1876
+ function publishAgentMemoryWritten(payload) {
1877
+ emit("agent", "memory.written", payload);
1878
+ }
1879
+ __name(publishAgentMemoryWritten, "publishAgentMemoryWritten");
1880
+ var AGENT_SPAN_EVENTS = [
1881
+ "llm.turn",
1882
+ "tool.execution",
1883
+ "retrieval",
1884
+ "follow-ups"
1885
+ ];
1886
+ var AGENT_DIAGNOSTIC_EVENTS = [
1887
+ "run.started",
1888
+ "message",
1889
+ "tool-call",
1890
+ "quota.exceeded",
1891
+ "run.finished",
1892
+ "run.failed",
1893
+ "delegated",
1894
+ "retrieved",
1895
+ "skills.resolved",
1896
+ "memory.resolved",
1897
+ "memory.written",
1898
+ "tool.retry"
1899
+ ];
1900
+ function agentDiagnosticKey(event) {
1901
+ return `agent:${event}`;
1902
+ }
1903
+ __name(agentDiagnosticKey, "agentDiagnosticKey");
1904
+
1905
+ // src/tool-retry.ts
1906
+ function hasTransientShape(error) {
1907
+ if (typeof error !== "object" || error === null) {
1908
+ return false;
1909
+ }
1910
+ const code = "code" in error ? error.code : void 0;
1911
+ const errno = "errno" in error ? error.errno : void 0;
1912
+ const sqlState = "sqlState" in error ? error.sqlState : void 0;
1913
+ if (code === 1213 || code === 1205 || errno === 1213 || errno === 1205) {
1914
+ return true;
1915
+ }
1916
+ if (code === "ER_LOCK_DEADLOCK" || code === "ER_LOCK_WAIT_TIMEOUT") {
1917
+ return true;
1918
+ }
1919
+ if (code === "40001" || code === "40P01" || sqlState === "40001" || sqlState === "40P01") {
1920
+ return true;
1921
+ }
1922
+ if (code === "SQLITE_BUSY") {
1923
+ return true;
1924
+ }
1925
+ const message = "message" in error ? error.message : void 0;
1926
+ return typeof message === "string" && /deadlock|lock wait timeout|serialization failure/i.test(message);
1927
+ }
1928
+ __name(hasTransientShape, "hasTransientShape");
1929
+ function isTransientToolError(error) {
1930
+ if (hasTransientShape(error)) {
1931
+ return true;
1932
+ }
1933
+ if (typeof error === "object" && error !== null && "cause" in error) {
1934
+ const cause = error.cause;
1935
+ if (cause !== void 0 && cause !== error && hasTransientShape(cause)) {
1936
+ return true;
1937
+ }
1938
+ }
1939
+ return false;
1940
+ }
1941
+ __name(isTransientToolError, "isTransientToolError");
1942
+ var DEFAULT_TOOL_TRANSIENT_RETRY_ATTEMPTS = 2;
1943
+ var DEFAULT_TOOL_TRANSIENT_RETRY_BACKOFF_MS = 150;
1944
+ function resolveToolTransientRetryNumbers(setting) {
1945
+ if (setting === false) {
1946
+ return false;
1947
+ }
1948
+ return {
1949
+ attempts: setting?.attempts ?? DEFAULT_TOOL_TRANSIENT_RETRY_ATTEMPTS,
1950
+ backoffMs: setting?.backoffMs ?? DEFAULT_TOOL_TRANSIENT_RETRY_BACKOFF_MS
1951
+ };
1952
+ }
1953
+ __name(resolveToolTransientRetryNumbers, "resolveToolTransientRetryNumbers");
1954
+ function delay(ms) {
1955
+ return new Promise((resolve) => {
1956
+ setTimeout(resolve, ms);
1957
+ });
1958
+ }
1959
+ __name(delay, "delay");
1960
+ async function invokeWithTransientRetry(fn, setting, options) {
1961
+ if (setting === false) {
1962
+ return fn();
1963
+ }
1964
+ const attempts = setting.attempts ?? DEFAULT_TOOL_TRANSIENT_RETRY_ATTEMPTS;
1965
+ const backoffMs = setting.backoffMs ?? DEFAULT_TOOL_TRANSIENT_RETRY_BACKOFF_MS;
1966
+ const classify = setting.classify ?? isTransientToolError;
1967
+ let attempt = 1;
1968
+ for (; ; ) {
1969
+ try {
1970
+ return await fn();
1971
+ } catch (error) {
1972
+ if (isControlFlowSignal(error) || options?.isControlFlowError?.(error) === true) {
1973
+ throw error;
1974
+ }
1975
+ const attemptsRemain = attempt < attempts;
1976
+ if (!attemptsRemain || !classify(error)) {
1977
+ throw error;
1978
+ }
1979
+ options?.onRetry?.(attempt, error);
1980
+ await delay(backoffMs * attempt);
1981
+ attempt += 1;
1982
+ }
1983
+ }
1984
+ }
1985
+ __name(invokeWithTransientRetry, "invokeWithTransientRetry");
1986
+
1987
+ // src/agent-loop.ts
1988
+ function resolveCostUsd(usage, reportedCostUsd, price) {
1989
+ if (reportedCostUsd !== void 0) {
1990
+ return reportedCostUsd;
1991
+ }
1992
+ return price === void 0 ? null : estimateCost(usage, price);
1993
+ }
1994
+ __name(resolveCostUsd, "resolveCostUsd");
1995
+ function buildContextBlock(passages) {
1996
+ const items = passages.map((passage, index) => {
1997
+ const label = passage.source !== void 0 ? ` (${passage.source})` : "";
1998
+ return `[${index + 1}]${label} ${passage.text}`;
1999
+ }).join("\n\n");
2000
+ return `<retrieved_context>
2001
+ ${items}
2002
+ </retrieved_context>
2003
+ Use the retrieved context above to answer when relevant, and cite sources by their bracket number.`;
2004
+ }
2005
+ __name(buildContextBlock, "buildContextBlock");
2006
+ function buildSummaryBlock(summary) {
2007
+ return `<conversation_summary>
2008
+ ${summary}
2009
+ </conversation_summary>
2010
+ Earlier messages in this thread are no longer included verbatim. Treat the summary above as an accurate record of them.`;
2011
+ }
2012
+ __name(buildSummaryBlock, "buildSummaryBlock");
2013
+ function settleAll(tasks) {
2014
+ const started = tasks.map((task) => task());
2015
+ return Promise.all(started.map((work) => work.then((value) => ({
2016
+ ok: true,
2017
+ value
2018
+ }), (error) => ({
2019
+ ok: false,
2020
+ error
2021
+ }))));
2022
+ }
2023
+ __name(settleAll, "settleAll");
2024
+ var QuotaExceededError = class extends Error {
2025
+ static {
2026
+ __name(this, "QuotaExceededError");
2027
+ }
2028
+ constructor() {
2029
+ super("Daily token quota exceeded");
2030
+ this.name = "QuotaExceededError";
2031
+ }
2032
+ };
2033
+ var RunCancelledError = class extends Error {
2034
+ static {
2035
+ __name(this, "RunCancelledError");
2036
+ }
2037
+ constructor() {
2038
+ super("Run cancelled");
2039
+ this.name = "RunCancelledError";
2040
+ }
2041
+ };
2042
+ function agentFailureCode(error) {
2043
+ if (error instanceof RunCancelledError) {
2044
+ return "cancelled";
2045
+ }
2046
+ if (error instanceof QuotaExceededError) {
2047
+ return "quota_exceeded";
2048
+ }
2049
+ if (error instanceof OutputRejectedError) {
2050
+ return "output_rejected";
2051
+ }
2052
+ if (error instanceof StructuredOutputError) {
2053
+ return "structured_output_invalid";
2054
+ }
2055
+ return "run_failed";
2056
+ }
2057
+ __name(agentFailureCode, "agentFailureCode");
2058
+ var MAX_DELEGATION_DEPTH = 5;
2059
+ var DEFAULT_MAX_AGENT_APPEARANCES = 1;
2060
+ function delegationRefusal(args) {
2061
+ const { deps, input, targetAgent } = args;
2062
+ const ancestry = [
2063
+ ...input.delegationPath ?? [],
2064
+ ...input.agentName !== void 0 ? [
2065
+ input.agentName
2066
+ ] : []
2067
+ ];
2068
+ const appearances = ancestry.filter((name) => name === targetAgent).length;
2069
+ const maxAppearances = deps.maxAgentAppearances ?? DEFAULT_MAX_AGENT_APPEARANCES;
2070
+ if (appearances >= maxAppearances) {
2071
+ const chain = [
2072
+ ...ancestry,
2073
+ targetAgent
2074
+ ].join(" \u2192 ");
2075
+ const times = appearances + 1;
2076
+ return `(delegation cycle: ${chain} \u2014 ${targetAgent} ${times} times on one chain)`;
2077
+ }
2078
+ const maxDepth = deps.maxDelegationDepth ?? MAX_DELEGATION_DEPTH;
2079
+ if ((input.delegationDepth ?? 0) >= maxDepth) {
2080
+ return `(delegation depth limit of ${maxDepth} reached)`;
2081
+ }
2082
+ return null;
2083
+ }
2084
+ __name(delegationRefusal, "delegationRefusal");
2085
+ async function resolvePrompt(prompt, ctx) {
2086
+ return typeof prompt === "function" ? prompt(ctx) : prompt;
2087
+ }
2088
+ __name(resolvePrompt, "resolvePrompt");
2089
+ async function resolveSystemPrompt(deps, input) {
2090
+ const ctx = {
2091
+ actor: input.actor,
2092
+ agentName: input.agentName ?? "default",
2093
+ ...input.pageContext !== void 0 ? {
2094
+ pageContext: input.pageContext
2095
+ } : {}
2096
+ };
2097
+ const sections = [
2098
+ await resolvePrompt(deps.systemPrompt, ctx)
2099
+ ];
2100
+ for (const contribute of deps.promptContributors ?? []) {
2101
+ const section = await contribute(ctx);
2102
+ if (section !== null && section.length > 0) {
2103
+ sections.push(section);
2104
+ }
2105
+ }
2106
+ return sections.join("\n\n");
2107
+ }
2108
+ __name(resolveSystemPrompt, "resolveSystemPrompt");
2109
+ function extractTask(input) {
2110
+ if (typeof input === "object" && input !== null && "task" in input) {
2111
+ const task = input.task;
2112
+ if (typeof task === "string") {
2113
+ return task;
2114
+ }
2115
+ }
2116
+ return JSON.stringify(input);
2117
+ }
2118
+ __name(extractTask, "extractTask");
2119
+ function deriveTitle(userText) {
2120
+ const trimmed = userText.trim().replace(/\s+/g, " ");
2121
+ return trimmed.length > 60 ? `${trimmed.slice(0, 57)}...` : trimmed || "New chat";
2122
+ }
2123
+ __name(deriveTitle, "deriveTitle");
2124
+ var ToolTimeoutError = class ToolTimeoutError2 extends Error {
2125
+ static {
2126
+ __name(this, "ToolTimeoutError");
2127
+ }
2128
+ constructor(toolName, ms) {
2129
+ super(`Tool "${toolName}" exceeded its ${ms}ms timeout`);
2130
+ this.name = "ToolTimeoutError";
2131
+ }
2132
+ };
2133
+ function withToolTimeout(work, ms, toolName) {
2134
+ return new Promise((resolve, reject) => {
2135
+ const timer = setTimeout(() => reject(new ToolTimeoutError(toolName, ms)), ms);
2136
+ work.then((value) => {
2137
+ clearTimeout(timer);
2138
+ resolve(value);
2139
+ }, (error) => {
2140
+ clearTimeout(timer);
2141
+ reject(error);
2142
+ });
2143
+ });
2144
+ }
2145
+ __name(withToolTimeout, "withToolTimeout");
2146
+ function parseFollowUps(text, count) {
2147
+ const source = text.match(/\[[\s\S]*\]/)?.[0] ?? text;
2148
+ try {
2149
+ const parsed = JSON.parse(source);
2150
+ if (Array.isArray(parsed)) {
2151
+ return parsed.filter((item) => typeof item === "string").slice(0, count);
2152
+ }
2153
+ } catch {
2154
+ }
2155
+ return [];
2156
+ }
2157
+ __name(parseFollowUps, "parseFollowUps");
2158
+ async function generateFollowUps(model, messages, count) {
2159
+ const discard = {
2160
+ write: /* @__PURE__ */ __name(() => {
2161
+ }, "write"),
2162
+ end: /* @__PURE__ */ __name(() => {
674
2163
  }, "end"),
675
2164
  fail: /* @__PURE__ */ __name(() => {
676
2165
  }, "fail")
677
2166
  };
678
- const turn = await model.runTurn({
679
- system: `Based on the conversation so far, propose up to ${count} short, distinct follow-up questions the user is likely to ask next. Respond with ONLY a JSON array of strings \u2014 no prose, no code fences.`,
680
- messages,
681
- tools: [],
682
- sink: discard
2167
+ const turn = await model.runTurn({
2168
+ system: `Based on the conversation so far, propose up to ${count} short, distinct follow-up questions the user is likely to ask next. Respond with ONLY a JSON array of strings \u2014 no prose, no code fences.`,
2169
+ messages,
2170
+ tools: [],
2171
+ sink: discard
2172
+ });
2173
+ return {
2174
+ followUps: parseFollowUps(turn.text, count),
2175
+ usage: turn.usage,
2176
+ ...turn.modelId !== void 0 ? {
2177
+ modelId: turn.modelId
2178
+ } : {}
2179
+ };
2180
+ }
2181
+ __name(generateFollowUps, "generateFollowUps");
2182
+ async function spanned(event, runId, payload, run, summarize) {
2183
+ let value;
2184
+ await trace("agent", event, async () => {
2185
+ value = await run();
2186
+ return summarize(value);
2187
+ }, payload, {
2188
+ traceId: runId
2189
+ });
2190
+ return value;
2191
+ }
2192
+ __name(spanned, "spanned");
2193
+ function traceLlmTurn(runId, step, run) {
2194
+ return spanned("llm.turn", runId, {
2195
+ runId,
2196
+ step
2197
+ }, run, (turn) => ({
2198
+ ...turn.modelId !== void 0 ? {
2199
+ modelId: turn.modelId
2200
+ } : {},
2201
+ inputTokens: turn.usage.inputTokens,
2202
+ outputTokens: turn.usage.outputTokens,
2203
+ textLength: turn.text.length,
2204
+ toolCalls: turn.toolCalls.length
2205
+ }));
2206
+ }
2207
+ __name(traceLlmTurn, "traceLlmTurn");
2208
+ function traceToolExecution(runId, call, run) {
2209
+ return spanned("tool.execution", runId, {
2210
+ runId,
2211
+ ...call
2212
+ }, run, () => ({}));
2213
+ }
2214
+ __name(traceToolExecution, "traceToolExecution");
2215
+ function toModelMessages(messages) {
2216
+ return messages.map((message) => ({
2217
+ role: message.role,
2218
+ content: message.content,
2219
+ ...message.toolCalls !== void 0 ? {
2220
+ toolCalls: message.toolCalls
2221
+ } : {},
2222
+ ...message.toolResults !== void 0 ? {
2223
+ toolResults: message.toolResults
2224
+ } : {},
2225
+ ...message.attachments !== void 0 ? {
2226
+ attachments: message.attachments
2227
+ } : {}
2228
+ }));
2229
+ }
2230
+ __name(toModelMessages, "toModelMessages");
2231
+ function historyContext(input) {
2232
+ return {
2233
+ threadId: input.threadId,
2234
+ actor: input.actor,
2235
+ ...input.agentName !== void 0 ? {
2236
+ agentName: input.agentName
2237
+ } : {}
2238
+ };
2239
+ }
2240
+ __name(historyContext, "historyContext");
2241
+ function splitHistory(policy, input, messages) {
2242
+ return policy === void 0 ? {
2243
+ keep: messages,
2244
+ drop: []
2245
+ } : policy.select(messages, historyContext(input));
2246
+ }
2247
+ __name(splitHistory, "splitHistory");
2248
+ async function loadSelectedHistory(deps, input, hooks) {
2249
+ return hooks.step("load:thread", async () => {
2250
+ const thread = await readThreadForTurn(deps, input.threadId);
2251
+ const { keep, drop } = splitHistory(deps.historyPolicy, input, toModelMessages(thread.messages));
2252
+ return {
2253
+ messages: keep,
2254
+ dropped: deps.historyPolicy?.summarize === void 0 ? [] : drop,
2255
+ title: thread.title,
2256
+ hasAssistantMessage: thread.hasAssistantMessage
2257
+ };
2258
+ });
2259
+ }
2260
+ __name(loadSelectedHistory, "loadSelectedHistory");
2261
+ async function readThreadForTurn(deps, threadId) {
2262
+ const windowing = deps.store;
2263
+ if (typeof windowing.loadThreadForTurn === "function") {
2264
+ const messageLimit = turnMessageLimit(deps.historyPolicy);
2265
+ const page = await windowing.loadThreadForTurn({
2266
+ threadId,
2267
+ ...messageLimit !== void 0 ? {
2268
+ messageLimit
2269
+ } : {}
2270
+ });
2271
+ return page === null ? {
2272
+ messages: [],
2273
+ title: null,
2274
+ hasAssistantMessage: false
2275
+ } : {
2276
+ messages: page.messages,
2277
+ title: page.title,
2278
+ hasAssistantMessage: page.hasAssistantMessage
2279
+ };
2280
+ }
2281
+ const thread = await deps.store.getThread(threadId);
2282
+ const stored = thread?.messages ?? [];
2283
+ return {
2284
+ messages: stored,
2285
+ title: thread?.title ?? null,
2286
+ hasAssistantMessage: stored.some((message) => message.role === "assistant")
2287
+ };
2288
+ }
2289
+ __name(readThreadForTurn, "readThreadForTurn");
2290
+ function turnMessageLimit(policy) {
2291
+ if (policy === void 0 || policy.summarize !== void 0) {
2292
+ return void 0;
2293
+ }
2294
+ return policy.maxMessages;
2295
+ }
2296
+ __name(turnMessageLimit, "turnMessageLimit");
2297
+ async function loadWholeThread(deps, input, hooks) {
2298
+ const thread = await hooks.step("load:thread", () => deps.store.getThread(input.threadId));
2299
+ const stored = thread?.messages ?? [];
2300
+ const { keep, drop } = splitHistory(deps.historyPolicy, input, toModelMessages(stored));
2301
+ return {
2302
+ messages: keep,
2303
+ dropped: drop,
2304
+ title: thread?.title ?? null,
2305
+ hasAssistantMessage: stored.some((message) => message.role === "assistant")
2306
+ };
2307
+ }
2308
+ __name(loadWholeThread, "loadWholeThread");
2309
+ async function foldDroppedHistory(summarize, deps, input, hooks, history) {
2310
+ const summary = await hooks.step("history:summarize", () => summarize(history.dropped, historyContext(input)));
2311
+ if (summary.usage !== void 0) {
2312
+ const usage = summary.usage;
2313
+ await hooks.step("persist:usage:history", () => deps.store.recordUsage({
2314
+ threadId: input.threadId,
2315
+ actorRef: input.actor.id,
2316
+ modelId: summary.modelId ?? deps.modelId ?? "unknown",
2317
+ purpose: "history_summary",
2318
+ usage
2319
+ }));
2320
+ }
2321
+ return [
2322
+ {
2323
+ role: "system",
2324
+ content: buildSummaryBlock(summary.text)
2325
+ },
2326
+ ...history.messages
2327
+ ];
2328
+ }
2329
+ __name(foldDroppedHistory, "foldDroppedHistory");
2330
+ function processorContext(input, step) {
2331
+ return {
2332
+ threadId: input.threadId,
2333
+ actor: input.actor,
2334
+ step,
2335
+ ...input.agentName !== void 0 ? {
2336
+ agentName: input.agentName
2337
+ } : {}
2338
+ };
2339
+ }
2340
+ __name(processorContext, "processorContext");
2341
+ function restatementPrompt(messages, answer, fromTranscript) {
2342
+ const restated = {
2343
+ role: "assistant",
2344
+ content: answer
2345
+ };
2346
+ if (fromTranscript) {
2347
+ return [
2348
+ ...messages,
2349
+ restated
2350
+ ];
2351
+ }
2352
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
2353
+ const message = messages[index];
2354
+ if (message?.role === "user") {
2355
+ return [
2356
+ message,
2357
+ restated
2358
+ ];
2359
+ }
2360
+ }
2361
+ return [
2362
+ restated
2363
+ ];
2364
+ }
2365
+ __name(restatementPrompt, "restatementPrompt");
2366
+ async function structureAnswer(schema, deps, input, hooks, messages, step) {
2367
+ const instruction = deps.outputInstruction ?? DEFAULT_STRUCTURED_OUTPUT_INSTRUCTION;
2368
+ const maxAttempts = 1 + (deps.outputRepairAttempts ?? 1);
2369
+ const outputProcessors = deps.outputProcessors ?? [];
2370
+ let issues = [];
2371
+ let text = "";
2372
+ for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
2373
+ const system = attempt === 0 ? instruction : repairInstruction(instruction, issues);
2374
+ const reply = await hooks.step(`structured:${step}:${attempt}`, () => spanned("structured-output", hooks.runId, {
2375
+ runId: hooks.runId,
2376
+ step,
2377
+ attempt
2378
+ }, async () => {
2379
+ const discard = {
2380
+ write: /* @__PURE__ */ __name(() => {
2381
+ }, "write"),
2382
+ end: /* @__PURE__ */ __name(() => {
2383
+ }, "end"),
2384
+ fail: /* @__PURE__ */ __name(() => {
2385
+ }, "fail")
2386
+ };
2387
+ const turn = await deps.model.runTurn({
2388
+ system,
2389
+ messages,
2390
+ tools: [],
2391
+ sink: discard,
2392
+ outputSchema: schema
2393
+ });
2394
+ return {
2395
+ text: turn.text,
2396
+ usage: turn.usage,
2397
+ ...turn.object !== void 0 ? {
2398
+ object: turn.object
2399
+ } : {},
2400
+ ...turn.modelId !== void 0 ? {
2401
+ modelId: turn.modelId
2402
+ } : {}
2403
+ };
2404
+ }, (result) => ({
2405
+ inputTokens: result.usage.inputTokens,
2406
+ outputTokens: result.usage.outputTokens,
2407
+ textLength: result.text.length
2408
+ })));
2409
+ await hooks.step(`persist:usage:structured:${step}:${attempt}`, () => deps.store.recordUsage({
2410
+ threadId: input.threadId,
2411
+ actorRef: input.actor.id,
2412
+ modelId: reply.modelId ?? deps.modelId ?? "unknown",
2413
+ purpose: "structured_output",
2414
+ usage: reply.usage
2415
+ }));
2416
+ text = reply.text;
2417
+ if (outputProcessors.length > 0) {
2418
+ const gate = await hooks.step(`process:output:structured:${step}:${attempt}`, () => runOutputProcessors(outputProcessors, {
2419
+ text: reply.text,
2420
+ toolCalls: []
2421
+ }, processorContext(input, step)));
2422
+ if (gate.rejection !== void 0) {
2423
+ throw new OutputRejectedError(gate.rejection.processor, gate.rejection.reason);
2424
+ }
2425
+ text = gate.text;
2426
+ }
2427
+ const reported = text === reply.text ? reply.object : void 0;
2428
+ const outcome = await validateStructured(schema, text, reported);
2429
+ if (outcome.ok) {
2430
+ return outcome.value;
2431
+ }
2432
+ issues = outcome.issues;
2433
+ }
2434
+ throw new StructuredOutputError(issues, text, maxAttempts);
2435
+ }
2436
+ __name(structureAnswer, "structureAnswer");
2437
+ function declaredKind(deps, name) {
2438
+ if (deps.ask === true && name === ASK_TOOL_NAME) {
2439
+ return "ask";
2440
+ }
2441
+ if (deps.skills !== void 0 && name === SKILL_TOOL_NAME) {
2442
+ return "skill";
2443
+ }
2444
+ if (memoryIsWritable(deps) && name === REMEMBER_TOOL_NAME) {
2445
+ return "memory";
2446
+ }
2447
+ return deps.registry.spec(name)?.kind ?? "read";
2448
+ }
2449
+ __name(declaredKind, "declaredKind");
2450
+ function memoryIsWritable(deps) {
2451
+ return deps.memory?.provider.write !== void 0;
2452
+ }
2453
+ __name(memoryIsWritable, "memoryIsWritable");
2454
+ function withAskTool({ tools, ask }) {
2455
+ return ask === true ? [
2456
+ ...tools,
2457
+ askToolDefinition()
2458
+ ] : tools;
2459
+ }
2460
+ __name(withAskTool, "withAskTool");
2461
+ async function awaitElicitation(hooks, request, ctx) {
2462
+ if (hooks.awaitAnswers !== void 0) {
2463
+ return normalizeElicitationReply(await hooks.awaitAnswers(request, ctx));
2464
+ }
2465
+ return normalizeElicitationReply(await hooks.awaitApproval({
2466
+ id: request.id,
2467
+ name: ASK_TOOL_NAME,
2468
+ input: request,
2469
+ kind: "ask"
2470
+ }, ctx));
2471
+ }
2472
+ __name(awaitElicitation, "awaitElicitation");
2473
+ function toolContext(deps, input, hooks) {
2474
+ return {
2475
+ actor: input.actor,
2476
+ threadId: input.threadId,
2477
+ runId: hooks.runId,
2478
+ requestId: hooks.runId,
2479
+ ...input.agentName !== void 0 ? {
2480
+ agentName: input.agentName
2481
+ } : {},
2482
+ ...input.pageContext !== void 0 ? {
2483
+ pageContext: input.pageContext
2484
+ } : {},
2485
+ ...deps.host !== void 0 ? {
2486
+ host: deps.host
2487
+ } : {}
2488
+ };
2489
+ }
2490
+ __name(toolContext, "toolContext");
2491
+ function intakeApplies(intake, threadHasAssistant) {
2492
+ return intake.when === "every-turn" || !threadHasAssistant;
2493
+ }
2494
+ __name(intakeApplies, "intakeApplies");
2495
+ async function runIntake(intake, deps, input, hooks, writer, threadHasAssistant) {
2496
+ const preamble = intake.preamble ?? DEFAULT_INTAKE_PREAMBLE;
2497
+ const request = {
2498
+ id: `intake-${hooks.runId}`,
2499
+ source: "intake",
2500
+ preamble,
2501
+ questions: intake.questions
2502
+ };
2503
+ const call = {
2504
+ id: request.id,
2505
+ name: ASK_TOOL_NAME,
2506
+ input: {
2507
+ preamble,
2508
+ questions: intake.questions
2509
+ },
2510
+ kind: "ask"
2511
+ };
2512
+ const asked = await hooks.step("intake:ask", async () => {
2513
+ if (!intakeApplies(intake, threadHasAssistant)) {
2514
+ return null;
2515
+ }
2516
+ const message = await deps.store.appendMessage({
2517
+ threadId: input.threadId,
2518
+ role: "assistant",
2519
+ content: preamble,
2520
+ runId: hooks.runId,
2521
+ toolCalls: [
2522
+ call
2523
+ ],
2524
+ ...input.agentName !== void 0 ? {
2525
+ agentName: input.agentName
2526
+ } : {}
2527
+ });
2528
+ await deps.store.recordToolCall({
2529
+ toolCallId: request.id,
2530
+ messageId: message.id,
2531
+ toolName: ASK_TOOL_NAME,
2532
+ // The store knows read/action only. An unanswered question is work waiting on a human, which
2533
+ // is what `action` + `pending_approval` already mean — so it surfaces in an approvals inbox
2534
+ // rather than needing one of its own.
2535
+ toolType: "action",
2536
+ input: call.input,
2537
+ status: "pending_approval",
2538
+ runId: hooks.runId
2539
+ });
2540
+ await writer.write(encodeStreamEvent({
2541
+ kind: "elicitation",
2542
+ id: request.id,
2543
+ request
2544
+ }));
2545
+ return {
2546
+ messageId: message.id
2547
+ };
2548
+ });
2549
+ if (asked === null) {
2550
+ return null;
2551
+ }
2552
+ const messageId = asked.messageId;
2553
+ const reply = await awaitElicitation(hooks, request, toolContext(deps, input, hooks));
2554
+ const result = settleElicitation(request, reply);
2555
+ await hooks.step("intake:answers", async () => {
2556
+ await deps.store.updateToolCall({
2557
+ toolCallId: request.id,
2558
+ // A skip is not an answer. Both leave the agent holding the same values, but only one of them
2559
+ // is evidence the user chose them, and a reader auditing what the agent was told has to be
2560
+ // able to tell those apart.
2561
+ status: result.skipped ? "rejected" : "executed",
2562
+ output: result,
2563
+ ...result.skipped ? {
2564
+ error: "skipped by the user"
2565
+ } : {},
2566
+ ...reply.answeredByRef !== void 0 ? {
2567
+ executedByRef: reply.answeredByRef
2568
+ } : {}
2569
+ });
2570
+ await deps.store.setMessageToolResults(messageId, [
2571
+ {
2572
+ id: request.id,
2573
+ name: ASK_TOOL_NAME,
2574
+ output: result
2575
+ }
2576
+ ]);
2577
+ await writer.write(encodeStreamEvent({
2578
+ kind: "tool-output",
2579
+ id: request.id,
2580
+ output: result
2581
+ }));
2582
+ });
2583
+ return {
2584
+ role: "assistant",
2585
+ content: preamble,
2586
+ toolCalls: [
2587
+ call
2588
+ ],
2589
+ toolResults: [
2590
+ {
2591
+ id: request.id,
2592
+ name: ASK_TOOL_NAME,
2593
+ output: result
2594
+ }
2595
+ ]
2596
+ };
2597
+ }
2598
+ __name(runIntake, "runIntake");
2599
+ var PARALLEL_TOOLS_PATCH = "agent:parallel-tools";
2600
+ var CANCELLATION_PATCH = "agent:cancellation";
2601
+ var SELECTED_HISTORY_PATCH = "agent:selected-history";
2602
+ var PROMPT_STAGES_PATCH = "agent:prompt-stages";
2603
+ async function resolvePromptStages(deps, hooks) {
2604
+ const configured = {
2605
+ memory: deps.memory !== void 0,
2606
+ retriever: deps.retriever !== void 0,
2607
+ skills: deps.skills !== void 0
2608
+ };
2609
+ return await (hooks.patched?.(PROMPT_STAGES_PATCH) ?? Promise.resolve(true)) ? hooks.step("run:prompt-stages", () => Promise.resolve(configured)) : configured;
2610
+ }
2611
+ __name(resolvePromptStages, "resolvePromptStages");
2612
+ var UNSERVED_MEMORY = {
2613
+ scopes: [],
2614
+ entries: [],
2615
+ omitted: 0,
2616
+ pinnedOmitted: 0
2617
+ };
2618
+ var UNSERVED_SKILLS = {
2619
+ scopes: [],
2620
+ entries: [],
2621
+ omitted: 0
2622
+ };
2623
+ async function haltIfCancelled(hooks, cancellable, name) {
2624
+ const observe = hooks.cancelled;
2625
+ if (!cancellable || observe === void 0) {
2626
+ return;
2627
+ }
2628
+ if (await hooks.step(name, () => observe())) {
2629
+ throw new RunCancelledError();
2630
+ }
2631
+ }
2632
+ __name(haltIfCancelled, "haltIfCancelled");
2633
+ async function claimToolCall(turn, call) {
2634
+ const { deps, input, hooks, messageId } = turn;
2635
+ const persisted = await hooks.step(`persist:toolcall:${call.id}`, async () => {
2636
+ const spec = deps.registry.spec(call.name);
2637
+ const kind = declaredKind(deps, call.name);
2638
+ const awaitsHuman = kind === "action" || kind === "ask";
2639
+ await deps.store.recordToolCall({
2640
+ toolCallId: call.id,
2641
+ messageId,
2642
+ toolName: call.name,
2643
+ toolType: awaitsHuman ? "action" : "read",
2644
+ input: call.input,
2645
+ status: awaitsHuman ? "pending_approval" : "auto_executed",
2646
+ runId: hooks.runId
2647
+ });
2648
+ return {
2649
+ kind,
2650
+ ...spec?.targetAgent !== void 0 ? {
2651
+ targetAgent: spec.targetAgent
2652
+ } : {},
2653
+ // Only an `agent` spec ever carries this, and only when the author declared it — so a
2654
+ // deployment with no detached edge writes the same bytes here it always has.
2655
+ ...spec?.detached === true ? {
2656
+ detached: true
2657
+ } : {}
2658
+ };
2659
+ });
2660
+ const toolType = persisted?.kind ?? call.kind ?? "read";
2661
+ return {
2662
+ call,
2663
+ // What the rest of the turn — the dispatched tool envelope, the stream frames — sees as this
2664
+ // call's kind, so it agrees with the branch actually taken instead of with a second lookup.
2665
+ resolvedCall: call.kind === toolType ? call : {
2666
+ ...call,
2667
+ kind: toolType
2668
+ },
2669
+ toolType,
2670
+ ...persisted?.targetAgent !== void 0 ? {
2671
+ targetAgent: persisted.targetAgent
2672
+ } : {},
2673
+ ...persisted?.detached === true ? {
2674
+ detached: true
2675
+ } : {},
2676
+ ctx: toolContext(deps, input, hooks)
2677
+ };
2678
+ }
2679
+ __name(claimToolCall, "claimToolCall");
2680
+ async function loadSkillIntoTurn(turn, claimed, startedAt) {
2681
+ const { deps, input, hooks } = turn;
2682
+ const { call } = claimed;
2683
+ const config = deps.skills;
2684
+ const offer = turn.skills;
2685
+ const outcome = await hooks.step(`tool:${call.id}`, async () => {
2686
+ if (config === void 0 || offer === void 0) {
2687
+ return {
2688
+ ok: false,
2689
+ error: "Skills are not available in this deployment."
2690
+ };
2691
+ }
2692
+ const parsed = await skillInputSchema["~standard"].validate(call.input);
2693
+ if (parsed.issues !== void 0) {
2694
+ return {
2695
+ ok: false,
2696
+ error: `invalid skill input: ${parsed.issues.map((each) => `${(each.path ?? []).join(".") || "(root)"}: ${each.message}`).join("; ")}`
2697
+ };
2698
+ }
2699
+ const loaded = await loadSkill(config, offer, parsed.value.name, skillContext(input));
2700
+ if (!loaded.ok) {
2701
+ return {
2702
+ ok: false,
2703
+ error: loaded.error
2704
+ };
2705
+ }
2706
+ return {
2707
+ ok: true,
2708
+ output: {
2709
+ name: loaded.skill.name,
2710
+ scope: loaded.skill.scope,
2711
+ body: loaded.skill.body,
2712
+ ...loaded.shadows !== void 0 ? {
2713
+ shadows: loaded.shadows
2714
+ } : {}
2715
+ }
2716
+ };
2717
+ });
2718
+ return outcome.ok ? {
2719
+ status: "executed",
2720
+ output: outcome.output,
2721
+ executionMs: Date.now() - startedAt
2722
+ } : {
2723
+ status: "failed",
2724
+ error: outcome.error,
2725
+ executionMs: Date.now() - startedAt
2726
+ };
2727
+ }
2728
+ __name(loadSkillIntoTurn, "loadSkillIntoTurn");
2729
+ async function rememberIntoTurn(turn, claimed, startedAt) {
2730
+ const { deps, input, hooks } = turn;
2731
+ const { call } = claimed;
2732
+ const config = deps.memory;
2733
+ const digest = turn.memory;
2734
+ const outcome = await hooks.step(`tool:${call.id}`, async () => {
2735
+ if (config === void 0 || digest === void 0) {
2736
+ return {
2737
+ ok: false,
2738
+ error: "Memory is not available in this deployment."
2739
+ };
2740
+ }
2741
+ const parsed = await rememberInputSchema["~standard"].validate(call.input);
2742
+ if (parsed.issues !== void 0) {
2743
+ return {
2744
+ ok: false,
2745
+ error: `invalid remember input: ${parsed.issues.map((each) => `${(each.path ?? []).join(".") || "(root)"}: ${each.message}`).join("; ")}`
2746
+ };
2747
+ }
2748
+ return await writeMemory({
2749
+ config,
2750
+ digest,
2751
+ call: parsed.value,
2752
+ ctx: skillContext(input),
2753
+ runId: hooks.runId
2754
+ });
2755
+ });
2756
+ if (!outcome.ok) {
2757
+ return {
2758
+ status: "failed",
2759
+ error: outcome.error,
2760
+ executionMs: Date.now() - startedAt
2761
+ };
2762
+ }
2763
+ publishAgentMemoryWritten({
2764
+ runId: hooks.runId,
2765
+ scope: outcome.record.scope,
2766
+ chars: outcome.record.text.length
2767
+ });
2768
+ return {
2769
+ status: "executed",
2770
+ output: outcome.record,
2771
+ executionMs: Date.now() - startedAt
2772
+ };
2773
+ }
2774
+ __name(rememberIntoTurn, "rememberIntoTurn");
2775
+ function skillContext(input) {
2776
+ return {
2777
+ actor: input.actor,
2778
+ threadId: input.threadId,
2779
+ ...input.agentName !== void 0 ? {
2780
+ agentName: input.agentName
2781
+ } : {},
2782
+ ...input.pageContext !== void 0 ? {
2783
+ pageContext: input.pageContext
2784
+ } : {}
2785
+ };
2786
+ }
2787
+ __name(skillContext, "skillContext");
2788
+ async function invokeClaimedTool(turn, claimed) {
2789
+ const { deps, input, hooks } = turn;
2790
+ const { call, resolvedCall, ctx } = claimed;
2791
+ const toolType = claimed.toolType === "action" ? "action" : "read";
2792
+ const startedAt = Date.now();
2793
+ try {
2794
+ if (claimed.toolType === "skill") {
2795
+ return await loadSkillIntoTurn(turn, claimed, startedAt);
2796
+ }
2797
+ if (claimed.toolType === "memory") {
2798
+ return await rememberIntoTurn(turn, claimed, startedAt);
2799
+ }
2800
+ let output;
2801
+ if (hooks.dispatchTool) {
2802
+ const stepCtx = {
2803
+ actor: input.actor,
2804
+ threadId: input.threadId,
2805
+ runId: hooks.runId,
2806
+ requestId: hooks.runId,
2807
+ ...input.agentName !== void 0 ? {
2808
+ agentName: input.agentName
2809
+ } : {},
2810
+ ...input.pageContext !== void 0 ? {
2811
+ pageContext: input.pageContext
2812
+ } : {}
2813
+ };
2814
+ const envelope = {
2815
+ toolName: call.name,
2816
+ input: call.input,
2817
+ ctx: stepCtx,
2818
+ ...deps.toolTimeoutMs !== void 0 ? {
2819
+ timeoutMs: deps.toolTimeoutMs
2820
+ } : {},
2821
+ // Numeric-only: the handler applies withToolTimeout AND its own local `classify` — see
2822
+ // ToolStepEnvelope.transientRetry.
2823
+ transientRetry: resolveToolTransientRetryNumbers(deps.toolTransientRetry)
2824
+ };
2825
+ output = await hooks.dispatchTool(resolvedCall, envelope);
2826
+ } else {
2827
+ const invocation = hooks.step(`tool:${call.id}`, () => traceToolExecution(hooks.runId, {
2828
+ toolCallId: call.id,
2829
+ toolName: call.name,
2830
+ toolType
2831
+ }, () => invokeWithTransientRetry(() => deps.registry.invoke(call.name, call.input, ctx, deps.rolesPolicy), deps.toolTransientRetry ?? {}, {
2832
+ ...hooks.isControlFlowError !== void 0 ? {
2833
+ isControlFlowError: hooks.isControlFlowError
2834
+ } : {},
2835
+ onRetry: /* @__PURE__ */ __name((attempt, retryError) => {
2836
+ publishAgentToolRetry({
2837
+ toolName: call.name,
2838
+ toolCallId: call.id,
2839
+ attempt,
2840
+ message: retryError instanceof Error ? retryError.message : String(retryError)
2841
+ });
2842
+ }, "onRetry")
2843
+ })));
2844
+ output = deps.toolTimeoutMs !== void 0 ? await withToolTimeout(invocation, deps.toolTimeoutMs, call.name) : await invocation;
2845
+ }
2846
+ return {
2847
+ status: "executed",
2848
+ output,
2849
+ executionMs: Date.now() - startedAt
2850
+ };
2851
+ } catch (error) {
2852
+ if (hooks.isControlFlowError?.(error) === true || isControlFlowSignal(error) || isReplayIntegrityError(error)) {
2853
+ throw error;
2854
+ }
2855
+ return {
2856
+ status: "failed",
2857
+ error: error instanceof Error ? error.message : String(error),
2858
+ executionMs: Date.now() - startedAt
2859
+ };
2860
+ }
2861
+ }
2862
+ __name(invokeClaimedTool, "invokeClaimedTool");
2863
+ async function recordToolOutcome(turn, claimed, outcome, deciderRef) {
2864
+ const { deps, hooks } = turn;
2865
+ const { call } = claimed;
2866
+ const toolType = claimed.toolType === "action" ? "action" : "read";
2867
+ if (outcome.status === "failed") {
2868
+ await hooks.step(`persist:toolfail:${call.id}`, () => deps.store.updateToolCall({
2869
+ toolCallId: call.id,
2870
+ status: "failed",
2871
+ error: outcome.error,
2872
+ executionMs: outcome.executionMs
2873
+ }));
2874
+ publishAgentToolCall({
2875
+ runId: hooks.runId,
2876
+ toolName: call.name,
2877
+ toolType,
2878
+ status: "failed",
2879
+ durationMs: outcome.executionMs
2880
+ });
2881
+ return {
2882
+ id: call.id,
2883
+ name: call.name,
2884
+ output: null,
2885
+ error: outcome.error
2886
+ };
2887
+ }
2888
+ await hooks.step(`persist:toolexec:${call.id}`, () => deps.store.updateToolCall({
2889
+ toolCallId: call.id,
2890
+ status: "executed",
2891
+ output: outcome.output,
2892
+ executionMs: outcome.executionMs,
2893
+ ...toolType === "action" ? {
2894
+ executedByRef: deciderRef
2895
+ } : {}
2896
+ }));
2897
+ publishAgentToolCall({
2898
+ runId: hooks.runId,
2899
+ toolName: call.name,
2900
+ toolType,
2901
+ status: "executed",
2902
+ durationMs: outcome.executionMs
2903
+ });
2904
+ return {
2905
+ id: call.id,
2906
+ name: call.name,
2907
+ output: outcome.output
2908
+ };
2909
+ }
2910
+ __name(recordToolOutcome, "recordToolOutcome");
2911
+ async function delegateToolCall(turn, claimed) {
2912
+ const { deps, input, hooks } = turn;
2913
+ const { call, targetAgent = call.name } = claimed;
2914
+ const task = extractTask(call.input);
2915
+ const refusal = delegationRefusal({
2916
+ deps,
2917
+ input,
2918
+ targetAgent
2919
+ });
2920
+ const start = claimed.detached === true && refusal === null ? hooks.startAgent : void 0;
2921
+ publishAgentDelegated({
2922
+ runId: hooks.runId,
2923
+ toAgent: targetAgent,
2924
+ ...input.agentName !== void 0 ? {
2925
+ fromAgent: input.agentName
2926
+ } : {},
2927
+ ...start !== void 0 ? {
2928
+ detached: true
2929
+ } : {}
683
2930
  });
2931
+ let sub;
2932
+ if (refusal !== null) {
2933
+ sub = {
2934
+ text: refusal
2935
+ };
2936
+ } else if (start !== void 0) {
2937
+ const started = await start({
2938
+ agentName: targetAgent,
2939
+ task,
2940
+ toolCallId: call.id
2941
+ });
2942
+ sub = detachedStarted({
2943
+ agent: targetAgent,
2944
+ runId: started.runId
2945
+ });
2946
+ } else if (hooks.runAgent) {
2947
+ sub = await hooks.runAgent(targetAgent, task);
2948
+ } else {
2949
+ sub = {
2950
+ text: `(no multi-agent support wired; cannot reach "${targetAgent}")`
2951
+ };
2952
+ }
2953
+ await hooks.step(`persist:toolexec:${call.id}`, () => deps.store.updateToolCall({
2954
+ toolCallId: call.id,
2955
+ status: "executed",
2956
+ output: sub
2957
+ }));
684
2958
  return {
685
- followUps: parseFollowUps(turn.text, count),
686
- usage: turn.usage,
687
- ...turn.modelId !== void 0 ? {
688
- modelId: turn.modelId
689
- } : {}
2959
+ id: call.id,
2960
+ name: call.name,
2961
+ output: sub
690
2962
  };
691
2963
  }
692
- __name(generateFollowUps, "generateFollowUps");
693
- async function spanned(event, runId, payload, run, summarize) {
694
- let value;
695
- await trace("agent", event, async () => {
696
- value = await run();
697
- return summarize(value);
698
- }, payload, {
699
- traceId: runId
2964
+ __name(delegateToolCall, "delegateToolCall");
2965
+ async function elicitToolCall(turn, claimed) {
2966
+ const { deps, hooks } = turn;
2967
+ const { call, ctx } = claimed;
2968
+ const parsed = await askInputSchema["~standard"].validate(call.input);
2969
+ if (parsed.issues !== void 0) {
2970
+ const error = `invalid ask input: ${parsed.issues.map((each) => `${(each.path ?? []).join(".") || "(root)"}: ${each.message}`).join("; ")}`;
2971
+ await hooks.step(`persist:toolfail:${call.id}`, () => deps.store.updateToolCall({
2972
+ toolCallId: call.id,
2973
+ status: "failed",
2974
+ error
2975
+ }));
2976
+ return {
2977
+ id: call.id,
2978
+ name: call.name,
2979
+ output: null,
2980
+ error
2981
+ };
2982
+ }
2983
+ const request = {
2984
+ id: call.id,
2985
+ source: "ask",
2986
+ questions: parsed.value.questions,
2987
+ ...parsed.value.preamble !== void 0 ? {
2988
+ preamble: parsed.value.preamble
2989
+ } : {}
2990
+ };
2991
+ await hooks.step(`stream:elicitation:${call.id}`, async () => {
2992
+ await turn.writer.write(encodeStreamEvent({
2993
+ kind: "elicitation",
2994
+ id: call.id,
2995
+ request
2996
+ }));
700
2997
  });
701
- return value;
702
- }
703
- __name(spanned, "spanned");
704
- function traceLlmTurn(runId, step, run) {
705
- return spanned("llm.turn", runId, {
706
- runId,
707
- step
708
- }, run, (turn) => ({
709
- ...turn.modelId !== void 0 ? {
710
- modelId: turn.modelId
2998
+ const reply = await awaitElicitation(hooks, request, ctx);
2999
+ const result = settleElicitation(request, reply);
3000
+ await hooks.step(result.skipped ? `persist:toolreject:${call.id}` : `persist:toolexec:${call.id}`, () => deps.store.updateToolCall({
3001
+ toolCallId: call.id,
3002
+ status: result.skipped ? "rejected" : "executed",
3003
+ output: result,
3004
+ ...result.skipped ? {
3005
+ error: "skipped by the user"
711
3006
  } : {},
712
- inputTokens: turn.usage.inputTokens,
713
- outputTokens: turn.usage.outputTokens,
714
- textLength: turn.text.length,
715
- toolCalls: turn.toolCalls.length
3007
+ ...reply.answeredByRef !== void 0 ? {
3008
+ executedByRef: reply.answeredByRef
3009
+ } : {}
716
3010
  }));
3011
+ publishAgentToolCall({
3012
+ runId: hooks.runId,
3013
+ toolName: call.name,
3014
+ toolType: "action",
3015
+ status: result.skipped ? "rejected" : "executed"
3016
+ });
3017
+ return {
3018
+ id: call.id,
3019
+ name: call.name,
3020
+ output: result
3021
+ };
717
3022
  }
718
- __name(traceLlmTurn, "traceLlmTurn");
719
- function traceToolExecution(runId, call, run) {
720
- return spanned("tool.execution", runId, {
721
- runId,
722
- ...call
723
- }, run, () => ({}));
3023
+ __name(elicitToolCall, "elicitToolCall");
3024
+ async function runClaimedToolCall(turn, claimed) {
3025
+ const { deps, input, hooks } = turn;
3026
+ const { call, toolType, ctx } = claimed;
3027
+ if (toolType === "agent") {
3028
+ return delegateToolCall(turn, claimed);
3029
+ }
3030
+ if (toolType === "ask") {
3031
+ return elicitToolCall(turn, claimed);
3032
+ }
3033
+ let deciderRef = input.actor.id;
3034
+ if (toolType === "action") {
3035
+ const decision = await hooks.awaitApproval(call, ctx);
3036
+ deciderRef = decision.executedByRef ?? input.actor.id;
3037
+ if (!decision.approved) {
3038
+ await hooks.step(`persist:toolreject:${call.id}`, () => deps.store.updateToolCall({
3039
+ toolCallId: call.id,
3040
+ status: "rejected",
3041
+ executedByRef: deciderRef,
3042
+ ...decision.reason !== void 0 ? {
3043
+ error: decision.reason
3044
+ } : {}
3045
+ }));
3046
+ publishAgentToolCall({
3047
+ runId: hooks.runId,
3048
+ toolName: call.name,
3049
+ toolType,
3050
+ status: "rejected"
3051
+ });
3052
+ return {
3053
+ id: call.id,
3054
+ name: call.name,
3055
+ output: {
3056
+ rejected: true,
3057
+ reason: decision.reason ?? "rejected by user"
3058
+ },
3059
+ error: "rejected"
3060
+ };
3061
+ }
3062
+ }
3063
+ return recordToolOutcome(turn, claimed, await invokeClaimedTool(turn, claimed), deciderRef);
724
3064
  }
725
- __name(traceToolExecution, "traceToolExecution");
3065
+ __name(runClaimedToolCall, "runClaimedToolCall");
3066
+ async function invokeClaimedToolsTogether(turn, parallel, claimed) {
3067
+ const settled = await parallel(claimed.map((entry) => () => invokeClaimedTool(turn, entry)));
3068
+ for (const outcome of settled) {
3069
+ if (!outcome.ok) {
3070
+ throw outcome.error;
3071
+ }
3072
+ }
3073
+ const results = [];
3074
+ for (const [index, entry] of claimed.entries()) {
3075
+ const outcome = settled[index];
3076
+ if (outcome?.ok === true) {
3077
+ results.push(await recordToolOutcome(turn, entry, outcome.value, turn.input.actor.id));
3078
+ }
3079
+ }
3080
+ return results;
3081
+ }
3082
+ __name(invokeClaimedToolsTogether, "invokeClaimedToolsTogether");
726
3083
  async function runAgentLoop(deps, input, hooks) {
727
3084
  const maxSteps = deps.maxSteps ?? 8;
728
3085
  let system = await resolveSystemPrompt(deps, input);
3086
+ const inputProcessors = deps.inputProcessors ?? [];
3087
+ const outputProcessors = deps.outputProcessors ?? [];
3088
+ const gateMode = resolveOutputGateMode(outputProcessors);
3089
+ const gated = gateMode !== "off";
3090
+ const gateLookback = resolveGateLookback(outputProcessors);
3091
+ let structured;
729
3092
  if (deps.quota !== void 0) {
730
3093
  const quota = deps.quota;
731
3094
  const state = await hooks.step("quota:check", () => quota.check(input.actor.id, deps.day));
@@ -759,25 +3122,18 @@ async function runAgentLoop(deps, input, hooks) {
759
3122
  threadId: input.threadId,
760
3123
  role: "user",
761
3124
  content: input.userText,
3125
+ runId: hooks.runId,
762
3126
  ...input.attachments !== void 0 ? {
763
3127
  attachments: input.attachments
764
3128
  } : {}
765
3129
  }));
766
3130
  }
767
- const thread = await hooks.step("load:thread", () => deps.store.getThread(input.threadId));
768
- const modelMessages = (thread?.messages ?? []).map((message) => ({
769
- role: message.role,
770
- content: message.content,
771
- ...message.toolCalls !== void 0 ? {
772
- toolCalls: message.toolCalls
773
- } : {},
774
- ...message.toolResults !== void 0 ? {
775
- toolResults: message.toolResults
776
- } : {},
777
- ...message.attachments !== void 0 ? {
778
- attachments: message.attachments
779
- } : {}
780
- }));
3131
+ const history = await (hooks.patched?.(SELECTED_HISTORY_PATCH) ?? Promise.resolve(true)) ? await loadSelectedHistory(deps, input, hooks) : await loadWholeThread(deps, input, hooks);
3132
+ let modelMessages = history.messages;
3133
+ const summarize = deps.historyPolicy?.summarize?.bind(deps.historyPolicy);
3134
+ if (summarize !== void 0 && history.dropped.length > 0) {
3135
+ modelMessages = await foldDroppedHistory(summarize, deps, input, hooks, history);
3136
+ }
781
3137
  const writer = await hooks.openSink();
782
3138
  let lastText = "";
783
3139
  let steps = 0;
@@ -791,6 +3147,7 @@ async function runAgentLoop(deps, input, hooks) {
791
3147
  agentName: input.agentName
792
3148
  } : {}
793
3149
  });
3150
+ const stages = await resolvePromptStages(deps, hooks);
794
3151
  const startedAt = await hooks.step("run:started-at", () => Promise.resolve(Date.now()));
795
3152
  await hooks.step("persist:run:start", async () => {
796
3153
  const promptHash = createHash("sha256").update(system).digest("hex");
@@ -801,20 +3158,52 @@ async function runAgentLoop(deps, input, hooks) {
801
3158
  ...input.agentName !== void 0 ? {
802
3159
  agentName: input.agentName
803
3160
  } : {},
3161
+ ...input.parentRunId !== void 0 ? {
3162
+ parentRunId: input.parentRunId
3163
+ } : {},
804
3164
  promptHash
805
3165
  });
806
3166
  });
3167
+ let memoryDigest;
3168
+ if (stages.memory) {
3169
+ const config = deps.memory;
3170
+ memoryDigest = await hooks.step("memory:digest", () => config === void 0 ? Promise.resolve(UNSERVED_MEMORY) : offerMemories({
3171
+ config,
3172
+ ctx: skillContext(input),
3173
+ query: input.userText
3174
+ }));
3175
+ const digest = memoryDigest;
3176
+ const block = digest.entries.length > 0 ? buildMemoryBlock({
3177
+ entries: digest.entries,
3178
+ writable: memoryIsWritable(deps),
3179
+ partial: digest.omitted > 0 || digest.recalled === true
3180
+ }) : "";
3181
+ if (block.length > 0) {
3182
+ system = `${system}
3183
+
3184
+ ${block}`;
3185
+ }
3186
+ publishAgentMemoryResolved({
3187
+ runId: hooks.runId,
3188
+ scopes: digest.scopes.length,
3189
+ offered: digest.entries.length,
3190
+ omitted: digest.omitted,
3191
+ pinnedOmitted: digest.pinnedOmitted,
3192
+ recalled: digest.recalled === true,
3193
+ promptChars: block.length
3194
+ });
3195
+ }
807
3196
  let injectedPassages;
808
- if (deps.retriever !== void 0) {
3197
+ if (stages.retriever) {
809
3198
  const retriever = deps.retriever;
810
3199
  const topK = deps.retrievalTopK ?? 5;
811
3200
  const passages = await hooks.step("retrieve", () => spanned("retrieval", hooks.runId, {
812
3201
  runId: hooks.runId,
813
3202
  queryLength: input.userText.length,
814
3203
  topK
815
- }, () => retriever.retrieve(input.userText, {
3204
+ }, () => retriever?.retrieve(input.userText, {
816
3205
  topK
817
- }), (retrieved) => ({
3206
+ }) ?? Promise.resolve([]), (retrieved) => ({
818
3207
  count: retrieved.length
819
3208
  })));
820
3209
  if (passages.length > 0) {
@@ -829,6 +3218,24 @@ ${buildContextBlock(passages)}`;
829
3218
  count: passages.length
830
3219
  });
831
3220
  }
3221
+ let skillOffer;
3222
+ if (stages.skills) {
3223
+ const config = deps.skills;
3224
+ skillOffer = await hooks.step("skills:catalog", () => config === void 0 ? Promise.resolve(UNSERVED_SKILLS) : offerSkills(config, skillContext(input)));
3225
+ const block = skillOffer.entries.length > 0 ? buildSkillsBlock(skillOffer.entries) : "";
3226
+ if (block.length > 0) {
3227
+ system = `${system}
3228
+
3229
+ ${block}`;
3230
+ }
3231
+ publishAgentSkillsResolved({
3232
+ runId: hooks.runId,
3233
+ scopes: skillOffer.scopes.length,
3234
+ offered: skillOffer.entries.length,
3235
+ omitted: skillOffer.omitted,
3236
+ promptChars: block.length
3237
+ });
3238
+ }
832
3239
  let prices = [];
833
3240
  if (deps.pricingStore !== void 0) {
834
3241
  const pricingStore = deps.pricingStore;
@@ -838,36 +3245,124 @@ ${buildContextBlock(passages)}`;
838
3245
  price.modelId,
839
3246
  price
840
3247
  ]));
3248
+ if (deps.intake !== void 0) {
3249
+ const asked = await runIntake(deps.intake, deps, input, hooks, writer, history.hasAssistantMessage);
3250
+ if (asked !== null) {
3251
+ modelMessages.push(asked);
3252
+ }
3253
+ }
3254
+ const cancellable = hooks.cancelled !== void 0 && await (hooks.patched?.(CANCELLATION_PATCH) ?? Promise.resolve(true));
841
3255
  for (let i = 0; i < maxSteps; i += 1) {
3256
+ await haltIfCancelled(hooks, cancellable, `cancel:check:${i}`);
842
3257
  await hooks.step(`stream:step-start:${i}`, async () => {
843
3258
  await writer.write(encodeStreamEvent({
844
3259
  kind: "step-start"
845
3260
  }));
846
3261
  });
3262
+ let prompt = {
3263
+ system,
3264
+ messages: modelMessages
3265
+ };
3266
+ if (inputProcessors.length > 0) {
3267
+ prompt = await hooks.step(`process:input:${i}`, () => runInputProcessors(inputProcessors, prompt, processorContext(input, i)));
3268
+ }
847
3269
  let turn;
848
3270
  if (hooks.dispatchLlm) {
849
3271
  turn = await hooks.dispatchLlm(i, {
850
3272
  ...input.agentName !== void 0 ? {
851
3273
  agentName: input.agentName
852
3274
  } : {},
853
- system,
854
- messages: modelMessages,
855
- actor: input.actor
3275
+ system: prompt.system,
3276
+ messages: prompt.messages,
3277
+ actor: input.actor,
3278
+ ...gated ? {
3279
+ bufferOutput: true
3280
+ } : {}
856
3281
  });
857
3282
  } else {
858
- const tools = await deps.registry.definitionsFor(input.actor, deps.rolesPolicy, deps.toolAllowList);
859
- turn = await hooks.step(`llm:${i}`, () => traceLlmTurn(hooks.runId, i, () => deps.model.runTurn({
860
- system,
861
- messages: modelMessages,
862
- tools,
863
- sink: writer
864
- })));
3283
+ const tools = withMemoryTool({
3284
+ tools: withSkillTool({
3285
+ tools: withAskTool({
3286
+ tools: await deps.registry.definitionsFor(input.actor, deps.rolesPolicy, deps.toolAllowList),
3287
+ ask: deps.ask
3288
+ }),
3289
+ enabled: deps.skills !== void 0
3290
+ }),
3291
+ enabled: memoryIsWritable(deps)
3292
+ });
3293
+ turn = await hooks.step(`llm:${i}`, async () => {
3294
+ const incremental = gateMode === "incremental" ? createIncrementalGate({
3295
+ processors: outputProcessors,
3296
+ ctx: processorContext(input, i),
3297
+ lookbackChars: gateLookback,
3298
+ writer
3299
+ }) : void 0;
3300
+ const buffer = gateMode === "whole" ? createFrameBuffer() : void 0;
3301
+ const result = await traceLlmTurn(hooks.runId, i, () => deps.model.runTurn({
3302
+ system: prompt.system,
3303
+ messages: prompt.messages,
3304
+ tools,
3305
+ sink: incremental?.writer ?? buffer?.writer ?? writer
3306
+ }));
3307
+ if (incremental !== void 0) {
3308
+ await incremental.settled();
3309
+ const refusal = incremental.rejection();
3310
+ return {
3311
+ ...result,
3312
+ releasedText: incremental.released(),
3313
+ ...refusal !== void 0 ? {
3314
+ gateRejection: refusal
3315
+ } : {}
3316
+ };
3317
+ }
3318
+ return buffer === void 0 ? result : {
3319
+ ...result,
3320
+ bufferedFrames: buffer.frames()
3321
+ };
3322
+ });
3323
+ }
3324
+ let rejection;
3325
+ if (gated) {
3326
+ const gate = await hooks.step(`process:output:${i}`, async () => {
3327
+ const released = turn.releasedText;
3328
+ if (turn.gateRejection !== void 0) {
3329
+ return {
3330
+ text: turn.text,
3331
+ rejection: turn.gateRejection
3332
+ };
3333
+ }
3334
+ const settled = await runOutputProcessors(outputProcessors, {
3335
+ text: turn.text,
3336
+ toolCalls: turn.toolCalls
3337
+ }, processorContext(input, i));
3338
+ if (settled.rejection === void 0) {
3339
+ if (released === void 0) {
3340
+ for (const chunk of releaseGatedFrames(turn.bufferedFrames ?? [], settled.text)) {
3341
+ await writer.write(chunk);
3342
+ }
3343
+ } else {
3344
+ const tail = gateTail(outputProcessors, released, settled.text);
3345
+ if (tail.length > 0) {
3346
+ await writer.write(encodeStreamEvent({
3347
+ kind: "text",
3348
+ text: tail
3349
+ }));
3350
+ }
3351
+ }
3352
+ }
3353
+ return settled;
3354
+ });
3355
+ rejection = gate.rejection;
3356
+ turn = {
3357
+ ...turn,
3358
+ text: gate.text
3359
+ };
865
3360
  }
866
3361
  const resolvedModelId = turn.modelId ?? deps.modelId ?? "unknown";
867
3362
  const costUsd = resolveCostUsd(turn.usage, turn.costUsd, priceByModel.get(resolvedModelId));
868
3363
  const toolCallsWithKind = turn.toolCalls.map((call) => ({
869
3364
  ...call,
870
- kind: deps.registry.spec(call.name)?.kind ?? "read"
3365
+ kind: declaredKind(deps, call.name)
871
3366
  }));
872
3367
  await hooks.step(`persist:usage:${i}`, () => deps.store.recordUsage({
873
3368
  threadId: input.threadId,
@@ -884,6 +3379,9 @@ ${buildContextBlock(passages)}`;
884
3379
  const quota = deps.quota;
885
3380
  await hooks.step(`quota:bump:${i}`, () => quota.bump(input.actor.id, deps.day, turn.usage.inputTokens + turn.usage.outputTokens));
886
3381
  }
3382
+ if (rejection !== void 0) {
3383
+ throw new OutputRejectedError(rejection.processor, rejection.reason);
3384
+ }
887
3385
  steps += 1;
888
3386
  totalInput += turn.usage.inputTokens;
889
3387
  totalOutput += turn.usage.outputTokens;
@@ -895,6 +3393,9 @@ ${buildContextBlock(passages)}`;
895
3393
  textLength: turn.text.length
896
3394
  });
897
3395
  const isFinalTurn = turn.toolCalls.length === 0;
3396
+ if (isFinalTurn && deps.outputSchema !== void 0) {
3397
+ structured = await structureAnswer(deps.outputSchema, deps, input, hooks, restatementPrompt(prompt.messages, turn.text, deps.outputFromTranscript === true), i);
3398
+ }
898
3399
  let followUps;
899
3400
  if (isFinalTurn && deps.followUpsCount !== void 0 && deps.followUpsCount > 0) {
900
3401
  const count = deps.followUpsCount;
@@ -916,8 +3417,12 @@ ${buildContextBlock(passages)}`;
916
3417
  modelId: result.modelId
917
3418
  } : {}
918
3419
  })));
919
- if (generated.followUps.length > 0) {
920
- followUps = generated.followUps;
3420
+ let suggestions = generated.followUps;
3421
+ if (outputProcessors.length > 0) {
3422
+ suggestions = await hooks.step(`process:output:followups:${i}`, () => gateFollowUps(outputProcessors, suggestions, processorContext(input, i)));
3423
+ }
3424
+ if (suggestions.length > 0) {
3425
+ followUps = suggestions;
921
3426
  }
922
3427
  await hooks.step(`persist:usage:followups:${i}`, () => deps.store.recordUsage({
923
3428
  threadId: input.threadId,
@@ -927,10 +3432,58 @@ ${buildContextBlock(passages)}`;
927
3432
  usage: generated.usage
928
3433
  }));
929
3434
  }
3435
+ const synthetic = [];
3436
+ if (i === 0 && injectedPassages !== void 0) {
3437
+ const call = {
3438
+ id: `retrieve-${hooks.runId}`,
3439
+ name: "retrieve",
3440
+ input: {
3441
+ query: input.userText
3442
+ },
3443
+ kind: "read"
3444
+ };
3445
+ const output = {
3446
+ passages: injectedPassages
3447
+ };
3448
+ synthetic.push({
3449
+ step: "persist:retrieval",
3450
+ call,
3451
+ result: {
3452
+ id: call.id,
3453
+ name: call.name,
3454
+ output
3455
+ }
3456
+ });
3457
+ }
3458
+ if (structured !== void 0) {
3459
+ const call = {
3460
+ id: `structured-${hooks.runId}`,
3461
+ name: "structured_output",
3462
+ input: {},
3463
+ kind: "read"
3464
+ };
3465
+ synthetic.push({
3466
+ step: "persist:structured",
3467
+ call,
3468
+ result: {
3469
+ id: call.id,
3470
+ name: call.name,
3471
+ output: structured
3472
+ }
3473
+ });
3474
+ }
3475
+ const messageCalls = [
3476
+ ...toolCallsWithKind,
3477
+ ...synthetic.map((entry) => entry.call)
3478
+ ];
3479
+ const syntheticResults = synthetic.map((entry) => entry.result);
930
3480
  const assistant = await hooks.step(`persist:assistant:${i}`, () => deps.store.appendMessage({
931
3481
  threadId: input.threadId,
932
3482
  role: "assistant",
933
3483
  content: turn.text,
3484
+ // Stamps which turn wrote this message, so a reader does not have to infer it from
3485
+ // timestamps — an inference a regenerate breaks, since it re-answers a surviving prompt.
3486
+ runId: hooks.runId,
934
3487
  usage: {
935
3488
  ...turn.usage,
936
3489
  costUsd
@@ -938,8 +3491,11 @@ ${buildContextBlock(passages)}`;
938
3491
  ...input.agentName !== void 0 ? {
939
3492
  agentName: input.agentName
940
3493
  } : {},
941
- ...toolCallsWithKind.length > 0 ? {
942
- toolCalls: toolCallsWithKind
3494
+ ...messageCalls.length > 0 ? {
3495
+ toolCalls: messageCalls
3496
+ } : {},
3497
+ ...syntheticResults.length > 0 ? {
3498
+ toolResults: syntheticResults
943
3499
  } : {},
944
3500
  ...followUps !== void 0 ? {
945
3501
  followUps
@@ -948,33 +3504,43 @@ ${buildContextBlock(passages)}`;
948
3504
  const assistantMessage = {
949
3505
  role: "assistant",
950
3506
  content: turn.text,
951
- ...toolCallsWithKind.length > 0 ? {
952
- toolCalls: toolCallsWithKind
3507
+ ...messageCalls.length > 0 ? {
3508
+ toolCalls: messageCalls
3509
+ } : {},
3510
+ ...syntheticResults.length > 0 ? {
3511
+ toolResults: syntheticResults
953
3512
  } : {}
954
3513
  };
955
3514
  modelMessages.push(assistantMessage);
956
- if (i === 0 && injectedPassages !== void 0) {
957
- const passages = injectedPassages;
958
- const toolCallId = `retrieve-${assistant.id}`;
959
- await hooks.step(`persist:retrieval:${assistant.id}`, async () => {
3515
+ for (const entry of synthetic) {
3516
+ const { call, result } = entry;
3517
+ await hooks.step(`${entry.step}:${assistant.id}`, async () => {
960
3518
  await deps.store.recordToolCall({
961
- toolCallId,
3519
+ toolCallId: call.id,
962
3520
  messageId: assistant.id,
963
- toolName: "retrieve",
3521
+ toolName: call.name,
964
3522
  toolType: "read",
965
- input: {
966
- query: input.userText
967
- },
3523
+ input: call.input,
968
3524
  status: "auto_executed",
969
3525
  runId: hooks.runId
970
3526
  });
971
3527
  await deps.store.updateToolCall({
972
- toolCallId,
3528
+ toolCallId: call.id,
973
3529
  status: "executed",
974
- output: {
975
- passages
976
- }
3530
+ output: result.output
977
3531
  });
3532
+ await writer.write(encodeStreamEvent({
3533
+ kind: "tool-input-available",
3534
+ id: call.id,
3535
+ name: call.name,
3536
+ input: call.input,
3537
+ toolKind: "read"
3538
+ }));
3539
+ await writer.write(encodeStreamEvent({
3540
+ kind: "tool-output",
3541
+ id: call.id,
3542
+ output: result.output
3543
+ }));
978
3544
  });
979
3545
  }
980
3546
  if (isFinalTurn) {
@@ -987,218 +3553,46 @@ ${buildContextBlock(passages)}`;
987
3553
  });
988
3554
  break;
989
3555
  }
3556
+ await haltIfCancelled(hooks, cancellable, `cancel:tools:${i}`);
990
3557
  const results = [];
991
- for (const call of toolCallsWithKind) {
992
- const spec = deps.registry.spec(call.name);
993
- const toolType = call.kind ?? "read";
994
- const ctx = {
995
- actor: input.actor,
996
- threadId: input.threadId,
997
- runId: hooks.runId,
998
- requestId: hooks.runId,
999
- ...input.agentName !== void 0 ? {
1000
- agentName: input.agentName
1001
- } : {},
1002
- ...input.pageContext !== void 0 ? {
1003
- pageContext: input.pageContext
1004
- } : {},
1005
- ...deps.host !== void 0 ? {
1006
- host: deps.host
1007
- } : {}
1008
- };
1009
- if (toolType === "agent") {
1010
- const targetAgent = spec?.targetAgent ?? call.name;
1011
- const task = extractTask(call.input);
1012
- await hooks.step(`persist:toolcall:${call.id}`, () => deps.store.recordToolCall({
1013
- toolCallId: call.id,
1014
- messageId: assistant.id,
1015
- toolName: call.name,
1016
- toolType: "read",
1017
- input: call.input,
1018
- status: "auto_executed",
1019
- runId: hooks.runId
1020
- }));
1021
- const overDepth = (input.delegationDepth ?? 0) >= MAX_DELEGATION_DEPTH;
1022
- publishAgentDelegated({
1023
- runId: hooks.runId,
1024
- toAgent: targetAgent,
1025
- ...input.agentName !== void 0 ? {
1026
- fromAgent: input.agentName
1027
- } : {}
1028
- });
1029
- let sub;
1030
- if (overDepth) {
1031
- sub = {
1032
- text: `(delegation depth limit of ${MAX_DELEGATION_DEPTH} reached)`
1033
- };
1034
- } else if (hooks.runAgent) {
1035
- sub = await hooks.runAgent(targetAgent, task);
1036
- } else {
1037
- sub = {
1038
- text: `(no multi-agent support wired; cannot reach "${targetAgent}")`
1039
- };
1040
- }
1041
- await hooks.step(`persist:toolexec:${call.id}`, () => deps.store.updateToolCall({
1042
- toolCallId: call.id,
1043
- status: "executed",
1044
- output: sub
1045
- }));
1046
- results.push({
1047
- id: call.id,
1048
- name: call.name,
1049
- output: sub
1050
- });
1051
- continue;
3558
+ const turnCalls = {
3559
+ deps,
3560
+ input,
3561
+ hooks,
3562
+ messageId: assistant.id,
3563
+ writer,
3564
+ ...skillOffer !== void 0 ? {
3565
+ skills: skillOffer
3566
+ } : {},
3567
+ ...memoryDigest !== void 0 ? {
3568
+ memory: memoryDigest
3569
+ } : {}
3570
+ };
3571
+ const parallel = hooks.parallel;
3572
+ if (parallel !== void 0 && toolCallsWithKind.length > 1 && await (hooks.patched?.(PARALLEL_TOOLS_PATCH) ?? Promise.resolve(true))) {
3573
+ const claimed = [];
3574
+ for (const call of toolCallsWithKind) {
3575
+ claimed.push(await claimToolCall(turnCalls, call));
1052
3576
  }
1053
- let deciderRef = input.actor.id;
1054
- if (toolType === "action") {
1055
- await hooks.step(`persist:toolcall:${call.id}`, () => deps.store.recordToolCall({
1056
- toolCallId: call.id,
1057
- messageId: assistant.id,
1058
- toolName: call.name,
1059
- toolType: "action",
1060
- input: call.input,
1061
- status: "pending_approval",
1062
- runId: hooks.runId
1063
- }));
1064
- const decision = await hooks.awaitApproval(call, ctx);
1065
- deciderRef = decision.executedByRef ?? input.actor.id;
1066
- if (!decision.approved) {
1067
- await hooks.step(`persist:toolreject:${call.id}`, () => deps.store.updateToolCall({
1068
- toolCallId: call.id,
1069
- status: "rejected",
1070
- executedByRef: deciderRef,
1071
- ...decision.reason !== void 0 ? {
1072
- error: decision.reason
1073
- } : {}
1074
- }));
1075
- results.push({
1076
- id: call.id,
1077
- name: call.name,
1078
- output: {
1079
- rejected: true,
1080
- reason: decision.reason ?? "rejected by user"
1081
- },
1082
- error: "rejected"
1083
- });
1084
- publishAgentToolCall({
1085
- runId: hooks.runId,
1086
- toolName: call.name,
1087
- toolType,
1088
- status: "rejected"
1089
- });
1090
- continue;
1091
- }
3577
+ if (claimed.every((entry) => entry.toolType === "read" || entry.toolType === "skill" || entry.toolType === "memory")) {
3578
+ results.push(...await invokeClaimedToolsTogether(turnCalls, parallel, claimed));
1092
3579
  } else {
1093
- await hooks.step(`persist:toolcall:${call.id}`, () => deps.store.recordToolCall({
1094
- toolCallId: call.id,
1095
- messageId: assistant.id,
1096
- toolName: call.name,
1097
- toolType: "read",
1098
- input: call.input,
1099
- status: "auto_executed",
1100
- runId: hooks.runId
1101
- }));
1102
- }
1103
- const startedAt2 = Date.now();
1104
- try {
1105
- let output;
1106
- if (hooks.dispatchTool) {
1107
- const stepCtx = {
1108
- actor: input.actor,
1109
- threadId: input.threadId,
1110
- runId: hooks.runId,
1111
- requestId: hooks.runId,
1112
- ...input.agentName !== void 0 ? {
1113
- agentName: input.agentName
1114
- } : {},
1115
- ...input.pageContext !== void 0 ? {
1116
- pageContext: input.pageContext
1117
- } : {}
1118
- };
1119
- const envelope = {
1120
- toolName: call.name,
1121
- input: call.input,
1122
- ctx: stepCtx,
1123
- ...deps.toolTimeoutMs !== void 0 ? {
1124
- timeoutMs: deps.toolTimeoutMs
1125
- } : {},
1126
- // Numeric-only: the handler applies withToolTimeout AND its own local `classify` — see
1127
- // ToolStepEnvelope.transientRetry.
1128
- transientRetry: resolveToolTransientRetryNumbers(deps.toolTransientRetry)
1129
- };
1130
- output = await hooks.dispatchTool(call, envelope);
1131
- } else {
1132
- const invocation = hooks.step(`tool:${call.id}`, () => traceToolExecution(hooks.runId, {
1133
- toolCallId: call.id,
1134
- toolName: call.name,
1135
- toolType
1136
- }, () => invokeWithTransientRetry(() => deps.registry.invoke(call.name, call.input, ctx, deps.rolesPolicy), deps.toolTransientRetry ?? {}, {
1137
- ...hooks.isControlFlowError !== void 0 ? {
1138
- isControlFlowError: hooks.isControlFlowError
1139
- } : {},
1140
- onRetry: /* @__PURE__ */ __name((attempt, retryError) => {
1141
- publishAgentToolRetry({
1142
- toolName: call.name,
1143
- toolCallId: call.id,
1144
- attempt,
1145
- message: retryError instanceof Error ? retryError.message : String(retryError)
1146
- });
1147
- }, "onRetry")
1148
- })));
1149
- output = deps.toolTimeoutMs !== void 0 ? await withToolTimeout(invocation, deps.toolTimeoutMs, call.name) : await invocation;
1150
- }
1151
- const executionMs = Date.now() - startedAt2;
1152
- await hooks.step(`persist:toolexec:${call.id}`, () => deps.store.updateToolCall({
1153
- toolCallId: call.id,
1154
- status: "executed",
1155
- output,
1156
- executionMs,
1157
- ...toolType === "action" ? {
1158
- executedByRef: deciderRef
1159
- } : {}
1160
- }));
1161
- results.push({
1162
- id: call.id,
1163
- name: call.name,
1164
- output
1165
- });
1166
- publishAgentToolCall({
1167
- runId: hooks.runId,
1168
- toolName: call.name,
1169
- toolType,
1170
- status: "executed",
1171
- durationMs: executionMs
1172
- });
1173
- } catch (error) {
1174
- if (hooks.isControlFlowError?.(error) === true) {
1175
- throw error;
3580
+ for (const entry of claimed) {
3581
+ results.push(await runClaimedToolCall(turnCalls, entry));
1176
3582
  }
1177
- const executionMs = Date.now() - startedAt2;
1178
- const message = error instanceof Error ? error.message : String(error);
1179
- await hooks.step(`persist:toolfail:${call.id}`, () => deps.store.updateToolCall({
1180
- toolCallId: call.id,
1181
- status: "failed",
1182
- error: message,
1183
- executionMs
1184
- }));
1185
- results.push({
1186
- id: call.id,
1187
- name: call.name,
1188
- output: null,
1189
- error: message
1190
- });
1191
- publishAgentToolCall({
1192
- runId: hooks.runId,
1193
- toolName: call.name,
1194
- toolType,
1195
- status: "failed",
1196
- durationMs: executionMs
1197
- });
3583
+ }
3584
+ } else {
3585
+ for (const call of toolCallsWithKind) {
3586
+ results.push(await runClaimedToolCall(turnCalls, await claimToolCall(turnCalls, call)));
1198
3587
  }
1199
3588
  }
1200
- assistantMessage.toolResults = results;
3589
+ const settledResults = [
3590
+ ...results,
3591
+ ...syntheticResults
3592
+ ];
3593
+ assistantMessage.toolResults = settledResults;
1201
3594
  await hooks.step(`stream:tool-outputs:${i}`, async () => {
3595
+ await deps.store.setMessageToolResults(assistant.id, settledResults);
1202
3596
  for (const result of results) {
1203
3597
  await writer.write(encodeStreamEvent(result.error !== void 0 ? {
1204
3598
  kind: "tool-output-error",
@@ -1219,9 +3613,34 @@ ${buildContextBlock(passages)}`;
1219
3613
  }));
1220
3614
  });
1221
3615
  }
1222
- if (thread !== null && (thread.title === "" || thread.title === "New chat")) {
3616
+ if (history.title === "" || history.title === "New chat") {
1223
3617
  await hooks.step("persist:title", () => deps.store.setTitle(input.threadId, deriveTitle(input.userText)));
1224
3618
  }
3619
+ const delivery = input.deliverTo;
3620
+ if (delivery !== void 0) {
3621
+ await hooks.step("deliver:detached", async () => {
3622
+ if (await deps.store.getThread(delivery.threadId) === null) {
3623
+ return;
3624
+ }
3625
+ const agent = input.agentName ?? "default";
3626
+ await deps.store.appendMessage({
3627
+ threadId: delivery.threadId,
3628
+ role: "assistant",
3629
+ content: lastText,
3630
+ agentName: agent,
3631
+ runId: hooks.runId
3632
+ });
3633
+ await deps.store.updateToolCall({
3634
+ toolCallId: delivery.toolCallId,
3635
+ status: "executed",
3636
+ output: detachedDelivered({
3637
+ agent,
3638
+ runId: hooks.runId,
3639
+ text: lastText
3640
+ })
3641
+ });
3642
+ });
3643
+ }
1225
3644
  await hooks.step("persist:run:end", async () => {
1226
3645
  await deps.store.recordRunEnd?.({
1227
3646
  runId: hooks.runId,
@@ -1238,7 +3657,10 @@ ${buildContextBlock(passages)}`;
1238
3657
  outputTokens: totalOutput
1239
3658
  });
1240
3659
  return {
1241
- text: lastText
3660
+ text: lastText,
3661
+ ...structured !== void 0 ? {
3662
+ object: structured
3663
+ } : {}
1242
3664
  };
1243
3665
  }
1244
3666
  __name(runAgentLoop, "runAgentLoop");
@@ -1252,6 +3674,7 @@ export {
1252
3674
  AGENT_DURABLE_RUNNER,
1253
3675
  AGENT_EMBEDDING_PROVIDER,
1254
3676
  AGENT_GOVERNANCE_QUERIES,
3677
+ AGENT_MEMORY,
1255
3678
  AGENT_MODEL,
1256
3679
  AGENT_OPTIONS,
1257
3680
  AGENT_PRICING_STORE,
@@ -1262,53 +3685,131 @@ export {
1262
3685
  AGENT_ROLES_POLICY,
1263
3686
  AGENT_RUNNER,
1264
3687
  AGENT_SINK,
3688
+ AGENT_SKILLS,
3689
+ AGENT_SKILL_SOURCES,
1265
3690
  AGENT_SPAN_EVENTS,
1266
3691
  AGENT_STORE,
1267
3692
  AGENT_TOOL_REGISTRY,
3693
+ ASK_TOOL_DESCRIPTION,
3694
+ ASK_TOOL_NAME,
1268
3695
  AgentRegistry,
1269
3696
  AgentStreamError,
3697
+ DEFAULT_HISTORY_SUMMARY_INSTRUCTION,
3698
+ DEFAULT_INCREMENTAL_LOOKBACK_CHARS,
3699
+ DEFAULT_INTAKE_PREAMBLE,
3700
+ DEFAULT_MAX_FACT_CHARS,
3701
+ DEFAULT_MAX_MEMORIES,
3702
+ DEFAULT_MAX_SKILLS,
3703
+ DEFAULT_STRUCTURED_OUTPUT_INSTRUCTION,
1270
3704
  DEFAULT_TOOL_TRANSIENT_RETRY_ATTEMPTS,
1271
3705
  DEFAULT_TOOL_TRANSIENT_RETRY_BACKOFF_MS,
1272
3706
  DefaultRolesPolicy,
3707
+ GLOBAL_SCOPE,
3708
+ MAX_ASK_QUESTIONS,
3709
+ OutputRejectedError,
3710
+ ProcessorFailedError,
1273
3711
  QuotaExceededError,
3712
+ REMEMBER_TOOL_DESCRIPTION,
3713
+ REMEMBER_TOOL_NAME,
3714
+ RunCancelledError,
3715
+ SKILL_TOOL_DESCRIPTION,
3716
+ SKILL_TOOL_NAME,
3717
+ StructuredOutputError,
1274
3718
  THREAD_DETAIL_CONTENT_CHARS,
1275
3719
  ToolDisabledError,
1276
3720
  ToolForbiddenError,
1277
3721
  ToolInputInvalidError,
1278
3722
  ToolNotFoundError,
1279
3723
  ToolRegistry,
3724
+ actorScope,
1280
3725
  agentDiagnosticKey,
3726
+ agentFailureCode,
3727
+ askInputSchema,
3728
+ askToolDefinition,
1281
3729
  bucketByActor,
1282
3730
  bucketByModel,
1283
3731
  bucketByThread,
1284
3732
  bucketUsageTrend,
3733
+ buildMemoryBlock,
3734
+ buildSkillsBlock,
1285
3735
  canActorUseTool,
3736
+ compositeSkillProvider,
3737
+ createFrameBuffer,
3738
+ createIncrementalGate,
1286
3739
  dayBoundsUtc,
3740
+ decodeStreamEvent,
3741
+ defaultScopeResolver,
3742
+ detachedDelivered,
3743
+ detachedStarted,
3744
+ detachedUnsettled,
1287
3745
  encodeStreamEvent,
1288
3746
  estimateCost,
3747
+ estimateMessageTokens,
3748
+ extractJson,
1289
3749
  filterToolsByAllowList,
1290
3750
  filterToolsByCanUse,
1291
3751
  filterToolsByEnabled,
1292
3752
  filterToolsByRole,
3753
+ gateFollowUps,
3754
+ gateTail,
1293
3755
  invokeWithTransientRetry,
3756
+ isControlFlowSignal,
3757
+ isReplayIntegrityError,
1294
3758
  isToolEnabled,
1295
3759
  isTransientToolError,
3760
+ loadSkill,
3761
+ memoryForgetVerdict,
3762
+ memoryWriteVerdict,
3763
+ normalizeDelegation,
3764
+ normalizeElicitationReply,
3765
+ offerMemories,
3766
+ offerSkills,
1296
3767
  publishAgentDelegated,
3768
+ publishAgentMemoryResolved,
3769
+ publishAgentMemoryWritten,
1297
3770
  publishAgentMessage,
1298
3771
  publishAgentQuotaExceeded,
1299
3772
  publishAgentRetrieved,
1300
3773
  publishAgentRunFailed,
1301
3774
  publishAgentRunFinished,
1302
3775
  publishAgentRunStarted,
3776
+ publishAgentSkillsResolved,
1303
3777
  publishAgentToolCall,
1304
3778
  publishAgentToolRetry,
3779
+ releaseGatedFrames,
3780
+ rememberInputSchema,
3781
+ rememberToolDefinition,
3782
+ renderElicitationAnswers,
3783
+ repairInstruction,
3784
+ resolveElicitation,
3785
+ resolveGateLookback,
3786
+ resolveMemoryDigest,
3787
+ resolveOutputGateMode,
3788
+ resolveSkillCatalog,
1305
3789
  resolveToolTransientRetryNumbers,
1306
3790
  rollupThreadUsage,
1307
3791
  runAgentLoop,
3792
+ runInputProcessors,
3793
+ runOutputProcessors,
1308
3794
  seedModelPrices,
3795
+ settleAll,
3796
+ settleElicitation,
3797
+ settleUnsettledDelegation,
3798
+ skillInputSchema,
3799
+ skillToolDefinition,
3800
+ skillWriteVerdict,
3801
+ staticSkillProvider,
3802
+ summarizeWithModel,
3803
+ tenantScope,
1309
3804
  traceLlmTurn,
1310
3805
  traceToolExecution,
1311
3806
  truncateDetailContent,
1312
- withToolTimeout
3807
+ validateStructured,
3808
+ windowHistory,
3809
+ withAskTool,
3810
+ withMemoryTool,
3811
+ withSkillTool,
3812
+ withToolTimeout,
3813
+ writeMemory
1313
3814
  };
1314
3815
  //# sourceMappingURL=index.js.map