@dudousxd/nestjs-agent-core 0.13.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -2614,9 +2614,9 @@ declare function tenantScope(tenantRef: string): string;
2614
2614
  *
2615
2615
  * A HOST-SUPPLIED FUNCTION rather than an enum this library owns, because the axes a deployment
2616
2616
  * scopes by are the deployment's own. `Actor` gives an id and a tenant; it does not give a sector, a
2617
- * squadron, a base, a shift — and every one of those is a real axis in some consumer. An enum here
2617
+ * region, a warehouse, a shift — and every one of those is a real axis in some consumer. An enum here
2618
2618
  * would make each of them a schema change in a library that has no business knowing they exist,
2619
- * while a token is a string a host mints for itself. Return `['sector:logistics', 'tenant:base-7',
2619
+ * while a token is a string a host mints for itself. Return `['sector:logistics', 'tenant:berlin',
2620
2620
  * 'global']` and precedence follows, with nothing in this package edited.
2621
2621
  *
2622
2622
  * MUST be a pure function of its context. It runs inside the `skills:catalog` checkpoint and its
@@ -2915,7 +2915,7 @@ interface MemoryRecord {
2915
2915
  /**
2916
2916
  * Always-on: this fact is in the block whether or not the turn is about it. The difference between
2917
2917
  * working memory and recall, drawn per record — "they report on the calendar year" must not depend
2918
- * on the turn mentioning dates, while "they prefer the shorter runway" can wait until it comes up.
2918
+ * on the turn mentioning dates, while "they prefer the kerbside dock" can wait until it comes up.
2919
2919
  *
2920
2920
  * A PROPERTY OF THE ROW, NOT OF A WRITE. This library reads it and never sets it, the same way it
2921
2921
  * never mints an `id`: an agent deciding its own conclusions are always-on is an agent deciding
@@ -2997,7 +2997,7 @@ interface ListMemoriesInput {
2997
2997
  *
2998
2998
  * `scopes` GATES, AND IT GATES FIRST. A search that ranks before it filters is a cross-tenant leak
2999
2999
  * wearing a relevance score: the nearest neighbour to "what is our rollback policy" is another
3000
- * base's rollback policy. Filter in the query, not after it. Records returned outside `scopes` are
3000
+ * tenant's rollback policy. Filter in the query, not after it. Records returned outside `scopes` are
3001
3001
  * dropped rather than trusted, so a mistake here costs throughput rather than privacy — but the
3002
3002
  * drop is a backstop, not the boundary.
3003
3003
  */
@@ -3470,6 +3470,16 @@ declare class ToolRegistry {
3470
3470
  private readonly entries;
3471
3471
  register(spec: ToolSpec, handler: ToolHandler): void;
3472
3472
  has(name: string): boolean;
3473
+ /**
3474
+ * Give a name back, and report whether it was held. For an importer that registered tools on
3475
+ * someone else's behalf — the MCP client is the one in this repo — and has to hand back the ones
3476
+ * its source stopped offering.
3477
+ *
3478
+ * The registry cannot tell whether a caller owns a name, so it does not try: whoever registered a
3479
+ * name is responsible for tracking that it did. Unregistering a name it does not own would
3480
+ * silently take a tool away from whoever does.
3481
+ */
3482
+ unregister(name: string): boolean;
3473
3483
  spec(name: string): ToolSpec | undefined;
3474
3484
  allSpecs(): ToolSpec[];
3475
3485
  /**
@@ -3569,7 +3579,9 @@ interface AgentLoopDeps<TOutput = unknown> {
3569
3579
  /**
3570
3580
  * Enables always-on ("inject") RAG: before the turn, retrieve passages for the user message and
3571
3581
  * augment the system prompt with them. Its presence IS inject mode — agentic (tool) retrieval sets
3572
- * no retriever here (it rides a normal `read` tool). Undefined → no injection.
3582
+ * no retriever here (it rides a normal `read` tool). Undefined → no injection, and no `retrieve`
3583
+ * position: which answer a RUN got is journaled (see {@link PromptStages}), so wiring one does not
3584
+ * move the checkpoints of a turn already in flight.
3573
3585
  */
3574
3586
  retriever?: Retriever;
3575
3587
  /** How many passages inject-mode retrieval requests. Undefined → 5. */
@@ -3673,7 +3685,9 @@ interface AgentLoopDeps<TOutput = unknown> {
3673
3685
  /**
3674
3686
  * Authored procedures the model may pull in when a task calls for one, resolved per turn against
3675
3687
  * the actor's scopes — see `skills.ts`. Undefined → no catalog block, no `skill` tool, and a
3676
- * turn's checkpoint sequence is byte-identical to one that never had the option.
3688
+ * turn's checkpoint sequence is byte-identical to one that never had the option — including for a
3689
+ * turn that was already in flight when this was wired, because which answer a RUN got is journaled
3690
+ * (see {@link PromptStages}).
3677
3691
  *
3678
3692
  * HOW IT COMPOSES WITH THE OTHER FOUR THINGS THAT WRITE THE PROMPT. The system block is assembled
3679
3693
  * in one fixed order — the agent's base prompt, then each `promptContributors` section, then
@@ -3692,7 +3706,8 @@ interface AgentLoopDeps<TOutput = unknown> {
3692
3706
  * What the assistant has previously concluded about the actor and their organisation, resolved per
3693
3707
  * turn against the same scope tokens skills use — see `memory.ts`. Undefined → no memory block, no
3694
3708
  * `remember` tool, and a turn's checkpoint sequence is byte-identical to one that never had the
3695
- * option.
3709
+ * option — including for a turn that was already in flight when this was wired, because which
3710
+ * answer a RUN got is journaled (see {@link PromptStages}).
3696
3711
  *
3697
3712
  * WHERE IT SITS IN THE PROMPT. The system block is assembled most-durable-first: the agent's base
3698
3713
  * prompt (the same for everyone, every turn), then `promptContributors`, then MEMORY (the same for
package/dist/index.d.ts CHANGED
@@ -2614,9 +2614,9 @@ declare function tenantScope(tenantRef: string): string;
2614
2614
  *
2615
2615
  * A HOST-SUPPLIED FUNCTION rather than an enum this library owns, because the axes a deployment
2616
2616
  * scopes by are the deployment's own. `Actor` gives an id and a tenant; it does not give a sector, a
2617
- * squadron, a base, a shift — and every one of those is a real axis in some consumer. An enum here
2617
+ * region, a warehouse, a shift — and every one of those is a real axis in some consumer. An enum here
2618
2618
  * would make each of them a schema change in a library that has no business knowing they exist,
2619
- * while a token is a string a host mints for itself. Return `['sector:logistics', 'tenant:base-7',
2619
+ * while a token is a string a host mints for itself. Return `['sector:logistics', 'tenant:berlin',
2620
2620
  * 'global']` and precedence follows, with nothing in this package edited.
2621
2621
  *
2622
2622
  * MUST be a pure function of its context. It runs inside the `skills:catalog` checkpoint and its
@@ -2915,7 +2915,7 @@ interface MemoryRecord {
2915
2915
  /**
2916
2916
  * Always-on: this fact is in the block whether or not the turn is about it. The difference between
2917
2917
  * working memory and recall, drawn per record — "they report on the calendar year" must not depend
2918
- * on the turn mentioning dates, while "they prefer the shorter runway" can wait until it comes up.
2918
+ * on the turn mentioning dates, while "they prefer the kerbside dock" can wait until it comes up.
2919
2919
  *
2920
2920
  * A PROPERTY OF THE ROW, NOT OF A WRITE. This library reads it and never sets it, the same way it
2921
2921
  * never mints an `id`: an agent deciding its own conclusions are always-on is an agent deciding
@@ -2997,7 +2997,7 @@ interface ListMemoriesInput {
2997
2997
  *
2998
2998
  * `scopes` GATES, AND IT GATES FIRST. A search that ranks before it filters is a cross-tenant leak
2999
2999
  * wearing a relevance score: the nearest neighbour to "what is our rollback policy" is another
3000
- * base's rollback policy. Filter in the query, not after it. Records returned outside `scopes` are
3000
+ * tenant's rollback policy. Filter in the query, not after it. Records returned outside `scopes` are
3001
3001
  * dropped rather than trusted, so a mistake here costs throughput rather than privacy — but the
3002
3002
  * drop is a backstop, not the boundary.
3003
3003
  */
@@ -3470,6 +3470,16 @@ declare class ToolRegistry {
3470
3470
  private readonly entries;
3471
3471
  register(spec: ToolSpec, handler: ToolHandler): void;
3472
3472
  has(name: string): boolean;
3473
+ /**
3474
+ * Give a name back, and report whether it was held. For an importer that registered tools on
3475
+ * someone else's behalf — the MCP client is the one in this repo — and has to hand back the ones
3476
+ * its source stopped offering.
3477
+ *
3478
+ * The registry cannot tell whether a caller owns a name, so it does not try: whoever registered a
3479
+ * name is responsible for tracking that it did. Unregistering a name it does not own would
3480
+ * silently take a tool away from whoever does.
3481
+ */
3482
+ unregister(name: string): boolean;
3473
3483
  spec(name: string): ToolSpec | undefined;
3474
3484
  allSpecs(): ToolSpec[];
3475
3485
  /**
@@ -3569,7 +3579,9 @@ interface AgentLoopDeps<TOutput = unknown> {
3569
3579
  /**
3570
3580
  * Enables always-on ("inject") RAG: before the turn, retrieve passages for the user message and
3571
3581
  * augment the system prompt with them. Its presence IS inject mode — agentic (tool) retrieval sets
3572
- * no retriever here (it rides a normal `read` tool). Undefined → no injection.
3582
+ * no retriever here (it rides a normal `read` tool). Undefined → no injection, and no `retrieve`
3583
+ * position: which answer a RUN got is journaled (see {@link PromptStages}), so wiring one does not
3584
+ * move the checkpoints of a turn already in flight.
3573
3585
  */
3574
3586
  retriever?: Retriever;
3575
3587
  /** How many passages inject-mode retrieval requests. Undefined → 5. */
@@ -3673,7 +3685,9 @@ interface AgentLoopDeps<TOutput = unknown> {
3673
3685
  /**
3674
3686
  * Authored procedures the model may pull in when a task calls for one, resolved per turn against
3675
3687
  * the actor's scopes — see `skills.ts`. Undefined → no catalog block, no `skill` tool, and a
3676
- * turn's checkpoint sequence is byte-identical to one that never had the option.
3688
+ * turn's checkpoint sequence is byte-identical to one that never had the option — including for a
3689
+ * turn that was already in flight when this was wired, because which answer a RUN got is journaled
3690
+ * (see {@link PromptStages}).
3677
3691
  *
3678
3692
  * HOW IT COMPOSES WITH THE OTHER FOUR THINGS THAT WRITE THE PROMPT. The system block is assembled
3679
3693
  * in one fixed order — the agent's base prompt, then each `promptContributors` section, then
@@ -3692,7 +3706,8 @@ interface AgentLoopDeps<TOutput = unknown> {
3692
3706
  * What the assistant has previously concluded about the actor and their organisation, resolved per
3693
3707
  * turn against the same scope tokens skills use — see `memory.ts`. Undefined → no memory block, no
3694
3708
  * `remember` tool, and a turn's checkpoint sequence is byte-identical to one that never had the
3695
- * option.
3709
+ * option — including for a turn that was already in flight when this was wired, because which
3710
+ * answer a RUN got is journaled (see {@link PromptStages}).
3696
3711
  *
3697
3712
  * WHERE IT SITS IN THE PROMPT. The system block is assembled most-durable-first: the agent's base
3698
3713
  * prompt (the same for everyone, every turn), then `promptContributors`, then MEMORY (the same for
package/dist/index.js CHANGED
@@ -1746,6 +1746,18 @@ var ToolRegistry = class {
1746
1746
  has(name) {
1747
1747
  return this.entries.has(name);
1748
1748
  }
1749
+ /**
1750
+ * Give a name back, and report whether it was held. For an importer that registered tools on
1751
+ * someone else's behalf — the MCP client is the one in this repo — and has to hand back the ones
1752
+ * its source stopped offering.
1753
+ *
1754
+ * The registry cannot tell whether a caller owns a name, so it does not try: whoever registered a
1755
+ * name is responsible for tracking that it did. Unregistering a name it does not own would
1756
+ * silently take a tool away from whoever does.
1757
+ */
1758
+ unregister(name) {
1759
+ return this.entries.delete(name);
1760
+ }
1749
1761
  spec(name) {
1750
1762
  return this.entries.get(name)?.spec;
1751
1763
  }
@@ -2059,7 +2071,12 @@ var MAX_DELEGATION_DEPTH = 5;
2059
2071
  var DEFAULT_MAX_AGENT_APPEARANCES = 1;
2060
2072
  function delegationRefusal(args) {
2061
2073
  const { deps, input, targetAgent } = args;
2062
- const ancestry = input.delegationPath ?? [];
2074
+ const ancestry = [
2075
+ ...input.delegationPath ?? [],
2076
+ ...input.agentName !== void 0 ? [
2077
+ input.agentName
2078
+ ] : []
2079
+ ];
2063
2080
  const appearances = ancestry.filter((name) => name === targetAgent).length;
2064
2081
  const maxAppearances = deps.maxAgentAppearances ?? DEFAULT_MAX_AGENT_APPEARANCES;
2065
2082
  if (appearances >= maxAppearances) {
@@ -2594,6 +2611,27 @@ __name(runIntake, "runIntake");
2594
2611
  var PARALLEL_TOOLS_PATCH = "agent:parallel-tools";
2595
2612
  var CANCELLATION_PATCH = "agent:cancellation";
2596
2613
  var SELECTED_HISTORY_PATCH = "agent:selected-history";
2614
+ var PROMPT_STAGES_PATCH = "agent:prompt-stages";
2615
+ async function resolvePromptStages(deps, hooks) {
2616
+ const configured = {
2617
+ memory: deps.memory !== void 0,
2618
+ retriever: deps.retriever !== void 0,
2619
+ skills: deps.skills !== void 0
2620
+ };
2621
+ return await (hooks.patched?.(PROMPT_STAGES_PATCH) ?? Promise.resolve(true)) ? hooks.step("run:prompt-stages", () => Promise.resolve(configured)) : configured;
2622
+ }
2623
+ __name(resolvePromptStages, "resolvePromptStages");
2624
+ var UNSERVED_MEMORY = {
2625
+ scopes: [],
2626
+ entries: [],
2627
+ omitted: 0,
2628
+ pinnedOmitted: 0
2629
+ };
2630
+ var UNSERVED_SKILLS = {
2631
+ scopes: [],
2632
+ entries: [],
2633
+ omitted: 0
2634
+ };
2597
2635
  async function haltIfCancelled(hooks, cancellable, name) {
2598
2636
  const observe = hooks.cancelled;
2599
2637
  if (!cancellable || observe === void 0) {
@@ -3121,6 +3159,7 @@ async function runAgentLoop(deps, input, hooks) {
3121
3159
  agentName: input.agentName
3122
3160
  } : {}
3123
3161
  });
3162
+ const stages = await resolvePromptStages(deps, hooks);
3124
3163
  const startedAt = await hooks.step("run:started-at", () => Promise.resolve(Date.now()));
3125
3164
  await hooks.step("persist:run:start", async () => {
3126
3165
  const promptHash = createHash("sha256").update(system).digest("hex");
@@ -3138,9 +3177,9 @@ async function runAgentLoop(deps, input, hooks) {
3138
3177
  });
3139
3178
  });
3140
3179
  let memoryDigest;
3141
- if (deps.memory !== void 0) {
3180
+ if (stages.memory) {
3142
3181
  const config = deps.memory;
3143
- memoryDigest = await hooks.step("memory:digest", () => offerMemories({
3182
+ memoryDigest = await hooks.step("memory:digest", () => config === void 0 ? Promise.resolve(UNSERVED_MEMORY) : offerMemories({
3144
3183
  config,
3145
3184
  ctx: skillContext(input),
3146
3185
  query: input.userText
@@ -3167,16 +3206,16 @@ ${block}`;
3167
3206
  });
3168
3207
  }
3169
3208
  let injectedPassages;
3170
- if (deps.retriever !== void 0) {
3209
+ if (stages.retriever) {
3171
3210
  const retriever = deps.retriever;
3172
3211
  const topK = deps.retrievalTopK ?? 5;
3173
3212
  const passages = await hooks.step("retrieve", () => spanned("retrieval", hooks.runId, {
3174
3213
  runId: hooks.runId,
3175
3214
  queryLength: input.userText.length,
3176
3215
  topK
3177
- }, () => retriever.retrieve(input.userText, {
3216
+ }, () => retriever?.retrieve(input.userText, {
3178
3217
  topK
3179
- }), (retrieved) => ({
3218
+ }) ?? Promise.resolve([]), (retrieved) => ({
3180
3219
  count: retrieved.length
3181
3220
  })));
3182
3221
  if (passages.length > 0) {
@@ -3192,9 +3231,9 @@ ${buildContextBlock(passages)}`;
3192
3231
  });
3193
3232
  }
3194
3233
  let skillOffer;
3195
- if (deps.skills !== void 0) {
3234
+ if (stages.skills) {
3196
3235
  const config = deps.skills;
3197
- skillOffer = await hooks.step("skills:catalog", () => offerSkills(config, skillContext(input)));
3236
+ skillOffer = await hooks.step("skills:catalog", () => config === void 0 ? Promise.resolve(UNSERVED_SKILLS) : offerSkills(config, skillContext(input)));
3198
3237
  const block = skillOffer.entries.length > 0 ? buildSkillsBlock(skillOffer.entries) : "";
3199
3238
  if (block.length > 0) {
3200
3239
  system = `${system}