agentfootprint 9.14.0 → 9.16.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.
Files changed (97) hide show
  1. package/AGENTS.md +1 -1
  2. package/CLAUDE.md +1 -1
  3. package/ai-instructions/claude-code/SKILL.md +1 -1
  4. package/dist/core/agent/AgentBuilder.js +130 -0
  5. package/dist/core/agent/AgentBuilder.js.map +1 -1
  6. package/dist/core/agent/buildAgentChart.js +3 -0
  7. package/dist/core/agent/buildAgentChart.js.map +1 -1
  8. package/dist/core/agent/buildDynamicAgentChart.js +6 -0
  9. package/dist/core/agent/buildDynamicAgentChart.js.map +1 -1
  10. package/dist/core/agent/stages/toolCalls.js +32 -1
  11. package/dist/core/agent/stages/toolCalls.js.map +1 -1
  12. package/dist/esm/core/agent/AgentBuilder.d.ts +24 -1
  13. package/dist/esm/core/agent/AgentBuilder.js +130 -0
  14. package/dist/esm/core/agent/AgentBuilder.js.map +1 -1
  15. package/dist/esm/core/agent/buildAgentChart.js +3 -0
  16. package/dist/esm/core/agent/buildAgentChart.js.map +1 -1
  17. package/dist/esm/core/agent/buildDynamicAgentChart.js +6 -0
  18. package/dist/esm/core/agent/buildDynamicAgentChart.js.map +1 -1
  19. package/dist/esm/core/agent/stages/toolCalls.d.ts +0 -3
  20. package/dist/esm/core/agent/stages/toolCalls.js +32 -1
  21. package/dist/esm/core/agent/stages/toolCalls.js.map +1 -1
  22. package/dist/esm/core/agent/types.d.ts +13 -1
  23. package/dist/esm/events/payloads.d.ts +35 -0
  24. package/dist/esm/events/registry.d.ts +3 -1
  25. package/dist/esm/events/registry.js +2 -0
  26. package/dist/esm/events/registry.js.map +1 -1
  27. package/dist/esm/lib/injection-engine/buildInjectionEngineSubflow.d.ts +1 -0
  28. package/dist/esm/lib/injection-engine/buildInjectionEngineSubflow.js +17 -0
  29. package/dist/esm/lib/injection-engine/buildInjectionEngineSubflow.js.map +1 -1
  30. package/dist/esm/lib/injection-engine/evaluator.d.ts +8 -3
  31. package/dist/esm/lib/injection-engine/evaluator.js +13 -7
  32. package/dist/esm/lib/injection-engine/evaluator.js.map +1 -1
  33. package/dist/esm/lib/injection-engine/factories/defineSkill.d.ts +28 -12
  34. package/dist/esm/lib/injection-engine/factories/defineSkill.js +32 -0
  35. package/dist/esm/lib/injection-engine/factories/defineSkill.js.map +1 -1
  36. package/dist/esm/lib/injection-engine/index.d.ts +2 -1
  37. package/dist/esm/lib/injection-engine/index.js +11 -1
  38. package/dist/esm/lib/injection-engine/index.js.map +1 -1
  39. package/dist/esm/lib/injection-engine/skillGraph.d.ts +140 -8
  40. package/dist/esm/lib/injection-engine/skillGraph.js +235 -39
  41. package/dist/esm/lib/injection-engine/skillGraph.js.map +1 -1
  42. package/dist/esm/lib/injection-engine/skillGraphCheckup.d.ts +11 -3
  43. package/dist/esm/lib/injection-engine/skillGraphCheckup.js +75 -2
  44. package/dist/esm/lib/injection-engine/skillGraphCheckup.js.map +1 -1
  45. package/dist/esm/lib/injection-engine/skillMatch.d.ts +107 -0
  46. package/dist/esm/lib/injection-engine/skillMatch.js +144 -0
  47. package/dist/esm/lib/injection-engine/skillMatch.js.map +1 -0
  48. package/dist/esm/lib/injection-engine/types.d.ts +39 -3
  49. package/dist/esm/lib/injection-engine/types.js +12 -0
  50. package/dist/esm/lib/injection-engine/types.js.map +1 -1
  51. package/dist/events/registry.js +2 -0
  52. package/dist/events/registry.js.map +1 -1
  53. package/dist/lib/injection-engine/buildInjectionEngineSubflow.js +17 -0
  54. package/dist/lib/injection-engine/buildInjectionEngineSubflow.js.map +1 -1
  55. package/dist/lib/injection-engine/evaluator.js +13 -7
  56. package/dist/lib/injection-engine/evaluator.js.map +1 -1
  57. package/dist/lib/injection-engine/factories/defineSkill.js +32 -0
  58. package/dist/lib/injection-engine/factories/defineSkill.js.map +1 -1
  59. package/dist/lib/injection-engine/index.js +12 -1
  60. package/dist/lib/injection-engine/index.js.map +1 -1
  61. package/dist/lib/injection-engine/skillGraph.js +236 -40
  62. package/dist/lib/injection-engine/skillGraph.js.map +1 -1
  63. package/dist/lib/injection-engine/skillGraphCheckup.js +75 -2
  64. package/dist/lib/injection-engine/skillGraphCheckup.js.map +1 -1
  65. package/dist/lib/injection-engine/skillMatch.js +150 -0
  66. package/dist/lib/injection-engine/skillMatch.js.map +1 -0
  67. package/dist/lib/injection-engine/types.js +14 -1
  68. package/dist/lib/injection-engine/types.js.map +1 -1
  69. package/dist/types/core/agent/AgentBuilder.d.ts +24 -1
  70. package/dist/types/core/agent/AgentBuilder.d.ts.map +1 -1
  71. package/dist/types/core/agent/buildAgentChart.d.ts.map +1 -1
  72. package/dist/types/core/agent/buildDynamicAgentChart.d.ts.map +1 -1
  73. package/dist/types/core/agent/stages/toolCalls.d.ts +0 -3
  74. package/dist/types/core/agent/stages/toolCalls.d.ts.map +1 -1
  75. package/dist/types/core/agent/types.d.ts +13 -1
  76. package/dist/types/core/agent/types.d.ts.map +1 -1
  77. package/dist/types/events/payloads.d.ts +35 -0
  78. package/dist/types/events/payloads.d.ts.map +1 -1
  79. package/dist/types/events/registry.d.ts +3 -1
  80. package/dist/types/events/registry.d.ts.map +1 -1
  81. package/dist/types/lib/injection-engine/buildInjectionEngineSubflow.d.ts +1 -0
  82. package/dist/types/lib/injection-engine/buildInjectionEngineSubflow.d.ts.map +1 -1
  83. package/dist/types/lib/injection-engine/evaluator.d.ts +8 -3
  84. package/dist/types/lib/injection-engine/evaluator.d.ts.map +1 -1
  85. package/dist/types/lib/injection-engine/factories/defineSkill.d.ts +28 -12
  86. package/dist/types/lib/injection-engine/factories/defineSkill.d.ts.map +1 -1
  87. package/dist/types/lib/injection-engine/index.d.ts +2 -1
  88. package/dist/types/lib/injection-engine/index.d.ts.map +1 -1
  89. package/dist/types/lib/injection-engine/skillGraph.d.ts +140 -8
  90. package/dist/types/lib/injection-engine/skillGraph.d.ts.map +1 -1
  91. package/dist/types/lib/injection-engine/skillGraphCheckup.d.ts +11 -3
  92. package/dist/types/lib/injection-engine/skillGraphCheckup.d.ts.map +1 -1
  93. package/dist/types/lib/injection-engine/skillMatch.d.ts +108 -0
  94. package/dist/types/lib/injection-engine/skillMatch.d.ts.map +1 -0
  95. package/dist/types/lib/injection-engine/types.d.ts +39 -3
  96. package/dist/types/lib/injection-engine/types.d.ts.map +1 -1
  97. package/package.json +1 -1
@@ -14,15 +14,21 @@
14
14
  * • `rule` → predicate runs against `ctx`. Errors are
15
15
  * caught + reported in `skipped`; never
16
16
  * propagate. Run never crashes.
17
- * • `on-tool-return` → active when `ctx.lastToolResult.toolName`
18
- * matches `trigger.toolName` (string equal
19
- * or regex test).
17
+ * • `on-tool-return` → active when ANY tool result of the previous
18
+ * iteration's batch (`toolResultsOf(ctx)` —
19
+ * `ctx.toolResults`, falling back to the
20
+ * singular `ctx.lastToolResult`) has a
21
+ * `toolName` matching `trigger.toolName`
22
+ * (string equal or regex test). Before 9.16.0
23
+ * only the LAST call of a parallel batch was
24
+ * consulted — earlier calls were dropped.
20
25
  * • `llm-activated` → active when the Injection's `id` is in
21
26
  * `ctx.activatedInjectionIds` (the LLM
22
27
  * previously called `viaToolName(<id>)`).
23
28
  */
24
29
  Object.defineProperty(exports, "__esModule", { value: true });
25
30
  exports.evaluateInjections = void 0;
31
+ const types_js_1 = require("./types.js");
26
32
  function evaluateInjections(injections, ctx) {
27
33
  const active = [];
28
34
  const skipped = [];
@@ -48,10 +54,10 @@ function evaluateInjections(injections, ctx) {
48
54
  break;
49
55
  }
50
56
  case 'on-tool-return': {
51
- const toolName = ctx.lastToolResult?.toolName;
52
- if (!toolName)
53
- break;
54
- const matches = typeof t.toolName === 'string' ? t.toolName === toolName : t.toolName.test(toolName);
57
+ // The WHOLE batch, in call order (9.16.0) — a trigger fires when any
58
+ // call of the previous iteration returned from the named tool, not
59
+ // only when that tool happened to be last in a parallel batch.
60
+ const matches = (0, types_js_1.toolResultsOf)(ctx).some((r) => typeof t.toolName === 'string' ? t.toolName === r.toolName : t.toolName.test(r.toolName));
55
61
  if (matches)
56
62
  active.push(inj);
57
63
  break;
@@ -1 +1 @@
1
- {"version":3,"file":"evaluator.js","sourceRoot":"","sources":["../../../src/lib/injection-engine/evaluator.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;;;AAIH,SAAgB,kBAAkB,CAChC,UAAgC,EAChC,GAAqB;IAErB,MAAM,MAAM,GAAgB,EAAE,CAAC;IAC/B,MAAM,OAAO,GAIR,EAAE,CAAC;IAER,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;QAC7B,MAAM,CAAC,GAAG,GAAG,CAAC,OAAO,CAAC;QACtB,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC;YACf,KAAK,QAAQ,CAAC,CAAC,CAAC;gBACd,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBACjB,MAAM;YACR,CAAC;YACD,KAAK,MAAM,CAAC,CAAC,CAAC;gBACZ,IAAI,CAAC;oBACH,IAAI,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC;wBAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBAC1C,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACb,OAAO,CAAC,IAAI,CAAC;wBACX,EAAE,EAAE,GAAG,CAAC,EAAE;wBACV,MAAM,EAAE,iBAAiB;wBACzB,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;qBACxD,CAAC,CAAC;gBACL,CAAC;gBACD,MAAM;YACR,CAAC;YACD,KAAK,gBAAgB,CAAC,CAAC,CAAC;gBACtB,MAAM,QAAQ,GAAG,GAAG,CAAC,cAAc,EAAE,QAAQ,CAAC;gBAC9C,IAAI,CAAC,QAAQ;oBAAE,MAAM;gBACrB,MAAM,OAAO,GACX,OAAO,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;gBACvF,IAAI,OAAO;oBAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBAC9B,MAAM;YACR,CAAC;YACD,KAAK,eAAe,CAAC,CAAC,CAAC;gBACrB,IAAI,GAAG,CAAC,qBAAqB,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;oBAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBACjE,MAAM;YACR,CAAC;YACD,OAAO,CAAC,CAAC,CAAC;gBACR,yDAAyD;gBACzD,+DAA+D;gBAC/D,MAAM,WAAW,GAAU,CAAC,CAAC;gBAC7B,OAAO,CAAC,IAAI,CAAC;oBACX,EAAE,EAAE,GAAG,CAAC,EAAE;oBACV,MAAM,EAAE,sBAAsB;oBAC9B,KAAK,EAAE,sBAAsB,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,EAAE;iBAC3D,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;AAC7B,CAAC;AAxDD,gDAwDC"}
1
+ {"version":3,"file":"evaluator.js","sourceRoot":"","sources":["../../../src/lib/injection-engine/evaluator.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;;;AAEH,yCAA2C;AAG3C,SAAgB,kBAAkB,CAChC,UAAgC,EAChC,GAAqB;IAErB,MAAM,MAAM,GAAgB,EAAE,CAAC;IAC/B,MAAM,OAAO,GAIR,EAAE,CAAC;IAER,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;QAC7B,MAAM,CAAC,GAAG,GAAG,CAAC,OAAO,CAAC;QACtB,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC;YACf,KAAK,QAAQ,CAAC,CAAC,CAAC;gBACd,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBACjB,MAAM;YACR,CAAC;YACD,KAAK,MAAM,CAAC,CAAC,CAAC;gBACZ,IAAI,CAAC;oBACH,IAAI,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC;wBAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBAC1C,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACb,OAAO,CAAC,IAAI,CAAC;wBACX,EAAE,EAAE,GAAG,CAAC,EAAE;wBACV,MAAM,EAAE,iBAAiB;wBACzB,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;qBACxD,CAAC,CAAC;gBACL,CAAC;gBACD,MAAM;YACR,CAAC;YACD,KAAK,gBAAgB,CAAC,CAAC,CAAC;gBACtB,qEAAqE;gBACrE,mEAAmE;gBACnE,+DAA+D;gBAC/D,MAAM,OAAO,GAAG,IAAA,wBAAa,EAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAC5C,OAAO,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CACzF,CAAC;gBACF,IAAI,OAAO;oBAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBAC9B,MAAM;YACR,CAAC;YACD,KAAK,eAAe,CAAC,CAAC,CAAC;gBACrB,IAAI,GAAG,CAAC,qBAAqB,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;oBAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBACjE,MAAM;YACR,CAAC;YACD,OAAO,CAAC,CAAC,CAAC;gBACR,yDAAyD;gBACzD,+DAA+D;gBAC/D,MAAM,WAAW,GAAU,CAAC,CAAC;gBAC7B,OAAO,CAAC,IAAI,CAAC;oBACX,EAAE,EAAE,GAAG,CAAC,EAAE;oBACV,MAAM,EAAE,sBAAsB;oBAC9B,KAAK,EAAE,sBAAsB,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,EAAE;iBAC3D,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;AAC7B,CAAC;AA1DD,gDA0DC"}
@@ -11,6 +11,16 @@
11
11
  * and callable from iteration 1 unless the Skill sets
12
12
  * `autoActivate: 'currentSkill'` — see that option.
13
13
  *
14
+ * **Activation lifetime: the rest of the turn.** Once the model picks a
15
+ * skill with `read_skill`, the activation lasts until the turn ends — every
16
+ * later iteration of that `run()` keeps the body active, and there is no
17
+ * mid-turn deactivation (nothing removes an id from
18
+ * `ctx.activatedInjectionIds`; the next `run()` starts clean). This is
19
+ * deliberate: the cache layer's skill history and the context ledger both
20
+ * lean on the cumulative property. A skill that should stop applying
21
+ * mid-turn is a `rule` trigger or a `skillGraph()` route, not a read_skill
22
+ * pick.
23
+ *
14
24
  * Produces an `Injection` with:
15
25
  * - flavor: `'skill'`
16
26
  * - trigger: `{ kind: 'llm-activated', viaToolName: 'read_skill' }`
@@ -34,6 +44,7 @@
34
44
  */
35
45
  Object.defineProperty(exports, "__esModule", { value: true });
36
46
  exports.defineSkill = exports.resolveSurfaceMode = void 0;
47
+ const footprintjs_1 = require("footprintjs");
37
48
  const applyCachePolicy_js_1 = require("../../../cache/applyCachePolicy.js");
38
49
  /**
39
50
  * Resolve `surfaceMode: 'auto'` to a concrete mode based on provider
@@ -89,6 +100,25 @@ function assertNoViaToolName(where, opts) {
89
100
  `skills already share ONE activation tool and the model picks WHICH skill by id. If you ` +
90
101
  `need the skill gated on something else, use a \`rule\` trigger or a skillGraph() edge.`);
91
102
  }
103
+ /** One warn per process for the deprecated `refreshPolicy` — the field is
104
+ * accepted (additive-only law) but a declaration that does nothing should
105
+ * say so ONCE, not on every skill of a 40-skill catalog. */
106
+ let warnedRefreshPolicyDeprecated = false;
107
+ /**
108
+ * The dev-mode deprecation warn for `refreshPolicy` (9.16.0). Named, not
109
+ * inlined, so the once-flag and the message live beside each other.
110
+ */
111
+ function warnRefreshPolicyDeprecated(skillId) {
112
+ if (warnedRefreshPolicyDeprecated || !(0, footprintjs_1.isDevMode)())
113
+ return;
114
+ warnedRefreshPolicyDeprecated = true;
115
+ // eslint-disable-next-line no-console
116
+ console.warn(`agentfootprint defineSkill('${skillId}'): \`refreshPolicy\` is DEPRECATED-pending-steps — ` +
117
+ 'it is stored on skill.metadata and nothing in the engine reads it, so NO re-injection ' +
118
+ 'happens. It is superseded by a planned steps-as-data feature and will not be wired up. ' +
119
+ "To re-surface a body in a long run today, use surfaceMode: 'both' (read_skill returns " +
120
+ 'the body afresh) or deliver it yourself. This warning fires once per process.');
121
+ }
92
122
  function defineSkill(opts) {
93
123
  if (!opts.id || opts.id.trim().length === 0) {
94
124
  throw new Error('defineSkill: `id` is required and must be non-empty.');
@@ -100,6 +130,8 @@ function defineSkill(opts) {
100
130
  throw new Error(`defineSkill(${opts.id}): \`body\` is required.`);
101
131
  }
102
132
  assertNoViaToolName(`defineSkill(${opts.id})`, opts);
133
+ if (opts.refreshPolicy)
134
+ warnRefreshPolicyDeprecated(opts.id);
103
135
  return Object.freeze({
104
136
  id: opts.id,
105
137
  description: opts.description,
@@ -1 +1 @@
1
- {"version":3,"file":"defineSkill.js","sourceRoot":"","sources":["../../../../src/lib/injection-engine/factories/defineSkill.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"defineSkill.js","sourceRoot":"","sources":["../../../../src/lib/injection-engine/factories/defineSkill.ts"],"names":[],"mappings":"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"}
@@ -7,10 +7,15 @@
7
7
  * factories. See `README.md` in this folder for the full concept.
8
8
  */
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.skillToolNames = exports.checkSkillContracts = exports.checkSkillContract = exports.rankEntries = exports.embeddingScorer = exports.keywordScorer = exports.SKILL_GRAPH_METADATA_KEY = exports.formatCheckup = exports.decideSkill = exports.skillGraph = exports.defineInjection = exports.defineFact = exports.defineSteering = exports.buildReadSkillTool = exports.buildListSkillsTool = exports.skillsFromDir = exports.SkillRegistry = exports.resolveSurfaceMode = exports.defineSkill = exports.READS_ENTRY_SCORES_METADATA_KEY = exports.defineRelevanceHint = exports.defineInstruction = exports.buildInjectionEngineSubflow = exports.evaluateInjections = exports.projectActiveInjection = void 0;
10
+ exports.skillToolNames = exports.checkSkillContracts = exports.checkSkillContract = exports.rankEntries = exports.embeddingScorer = exports.keywordScorer = exports.SKILL_GRAPH_DEFERRED_CONTRACT_KEY = exports.SKILL_GRAPH_METADATA_KEY = exports.formatCheckup = exports.decideSkill = exports.skillGraph = exports.defineInjection = exports.defineFact = exports.defineSteering = exports.buildReadSkillTool = exports.buildListSkillsTool = exports.skillsFromDir = exports.SkillRegistry = exports.resolveSurfaceMode = exports.defineSkill = exports.READS_ENTRY_SCORES_METADATA_KEY = exports.defineRelevanceHint = exports.defineInstruction = exports.buildInjectionEngineSubflow = exports.evaluateInjections = exports.toolResultsOf = exports.projectActiveInjection = void 0;
11
11
  // POJO projection — used by slot subflows + advanced consumers
12
12
  var types_js_1 = require("./types.js");
13
13
  Object.defineProperty(exports, "projectActiveInjection", { enumerable: true, get: function () { return types_js_1.projectActiveInjection; } });
14
+ // The iteration's tool-result batch with the singular fallback applied
15
+ // (9.16.0) — the one reader `rule` predicates should use to see every call of
16
+ // a parallel batch, not only the last (`ctx.toolResults ?? [ctx.lastToolResult]`).
17
+ var types_js_2 = require("./types.js");
18
+ Object.defineProperty(exports, "toolResultsOf", { enumerable: true, get: function () { return types_js_2.toolResultsOf; } });
14
19
  // Engine
15
20
  var evaluator_js_1 = require("./evaluator.js");
16
21
  Object.defineProperty(exports, "evaluateInjections", { enumerable: true, get: function () { return evaluator_js_1.evaluateInjections; } });
@@ -59,6 +64,12 @@ Object.defineProperty(exports, "decideSkill", { enumerable: true, get: function
59
64
  // is already the door to it.
60
65
  Object.defineProperty(exports, "formatCheckup", { enumerable: true, get: function () { return skillGraph_js_1.formatCheckup; } });
61
66
  Object.defineProperty(exports, "SKILL_GRAPH_METADATA_KEY", { enumerable: true, get: function () { return skillGraph_js_1.SKILL_GRAPH_METADATA_KEY; } });
67
+ // The graph's note that its body-contract checks wait for Agent build (present
68
+ // only when built without `knownTools`) — see SkillGraph.deferredBodyContract.
69
+ // The note rides the graph AND each compiled skill's metadata (under
70
+ // SKILL_GRAPH_DEFERRED_CONTRACT_KEY), so Agent build honors it whichever way
71
+ // the skills arrive — `.skillGraph(graph)` or `.skills({ list: () => graph.skills })`.
72
+ Object.defineProperty(exports, "SKILL_GRAPH_DEFERRED_CONTRACT_KEY", { enumerable: true, get: function () { return skillGraph_js_1.SKILL_GRAPH_DEFERRED_CONTRACT_KEY; } });
62
73
  var entryScorer_js_1 = require("./entryScorer.js");
63
74
  Object.defineProperty(exports, "keywordScorer", { enumerable: true, get: function () { return entryScorer_js_1.keywordScorer; } });
64
75
  Object.defineProperty(exports, "embeddingScorer", { enumerable: true, get: function () { return entryScorer_js_1.embeddingScorer; } });
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/injection-engine/index.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;;AAYH,+DAA+D;AAC/D,uCAAoD;AAA3C,kHAAA,sBAAsB,OAAA;AAE/B,SAAS;AACT,+CAAoD;AAA3C,kHAAA,kBAAkB,OAAA;AAC3B,mFAG0C;AAFxC,6IAAA,2BAA2B,OAAA;AAI7B,gFAAgF;AAChF,yEAAoG;AAA3F,yHAAA,iBAAiB,OAAA;AAC1B,6EAI4C;AAH1C,6HAAA,mBAAmB,OAAA;AACnB,yIAAA,+BAA+B,OAAA;AAIjC,6DAOoC;AANlC,6GAAA,WAAW,OAAA;AACX,oHAAA,kBAAkB,OAAA;AAOpB,uDAA8E;AAArE,iHAAA,aAAa,OAAA;AAEtB,8EAA8E;AAC9E,+EAA+E;AAC/E,6DAA6D;AAC7D,uDAA8E;AAArE,iHAAA,aAAa,OAAA;AAEtB,wEAAwE;AACxE,sEAAsE;AACtE,iEAAiE;AACjE,+BAA+B;AAC/B,iDAA8F;AAArF,oHAAA,mBAAmB,OAAA;AAAE,mHAAA,kBAAkB,OAAA;AAEhD,mEAA2F;AAAlF,mHAAA,cAAc,OAAA;AAEvB,2DAA+E;AAAtE,2GAAA,UAAU,OAAA;AAEnB,6EAA6E;AAC7E,0EAA0E;AAC1E,qDAAqD;AACrD,qEAIwC;AAHtC,qHAAA,eAAe,OAAA;AAKjB,4EAA4E;AAC5E,8EAA8E;AAC9E,iDAqCyB;AApCvB,2GAAA,UAAU,OAAA;AACV,4GAAA,WAAW,OAAA;AACX,mFAAmF;AACnF,qFAAqF;AACrF,uFAAuF;AACvF,6BAA6B;AAC7B,8GAAA,aAAa,OAAA;AACb,yHAAA,wBAAwB,OAAA;AA8B1B,mDAO0B;AANxB,+GAAA,aAAa,OAAA;AACb,iHAAA,eAAe,OAAA;AACf,6GAAA,WAAW,OAAA;AAKb,uDAK4B;AAJ1B,sHAAA,kBAAkB,OAAA;AAClB,uHAAA,mBAAmB,OAAA;AACnB,kHAAA,cAAc,OAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/injection-engine/index.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;;AAYH,+DAA+D;AAC/D,uCAAoD;AAA3C,kHAAA,sBAAsB,OAAA;AAE/B,uEAAuE;AACvE,8EAA8E;AAC9E,mFAAmF;AACnF,uCAA2C;AAAlC,yGAAA,aAAa,OAAA;AAEtB,SAAS;AACT,+CAAoD;AAA3C,kHAAA,kBAAkB,OAAA;AAC3B,mFAG0C;AAFxC,6IAAA,2BAA2B,OAAA;AAI7B,gFAAgF;AAChF,yEAAoG;AAA3F,yHAAA,iBAAiB,OAAA;AAC1B,6EAI4C;AAH1C,6HAAA,mBAAmB,OAAA;AACnB,yIAAA,+BAA+B,OAAA;AAIjC,6DAOoC;AANlC,6GAAA,WAAW,OAAA;AACX,oHAAA,kBAAkB,OAAA;AAOpB,uDAA8E;AAArE,iHAAA,aAAa,OAAA;AAEtB,8EAA8E;AAC9E,+EAA+E;AAC/E,6DAA6D;AAC7D,uDAA8E;AAArE,iHAAA,aAAa,OAAA;AAEtB,wEAAwE;AACxE,sEAAsE;AACtE,iEAAiE;AACjE,+BAA+B;AAC/B,iDAA8F;AAArF,oHAAA,mBAAmB,OAAA;AAAE,mHAAA,kBAAkB,OAAA;AAEhD,mEAA2F;AAAlF,mHAAA,cAAc,OAAA;AAEvB,2DAA+E;AAAtE,2GAAA,UAAU,OAAA;AAEnB,6EAA6E;AAC7E,0EAA0E;AAC1E,qDAAqD;AACrD,qEAIwC;AAHtC,qHAAA,eAAe,OAAA;AAKjB,4EAA4E;AAC5E,8EAA8E;AAC9E,iDAsDyB;AArDvB,2GAAA,UAAU,OAAA;AACV,4GAAA,WAAW,OAAA;AACX,mFAAmF;AACnF,qFAAqF;AACrF,uFAAuF;AACvF,6BAA6B;AAC7B,8GAAA,aAAa,OAAA;AACb,yHAAA,wBAAwB,OAAA;AAiCxB,+EAA+E;AAC/E,+EAA+E;AAC/E,qEAAqE;AACrE,6EAA6E;AAC7E,uFAAuF;AACvF,kIAAA,iCAAiC,OAAA;AASnC,mDAO0B;AANxB,+GAAA,aAAa,OAAA;AACb,iHAAA,eAAe,OAAA;AACf,6GAAA,WAAW,OAAA;AAKb,uDAK4B;AAJ1B,sHAAA,kBAAkB,OAAA;AAClB,uHAAA,mBAAmB,OAAA;AACnB,kHAAA,cAAc,OAAA"}
@@ -59,11 +59,13 @@
59
59
  * per-hop cause, so a model pick was recorded under a declared edge's label).
60
60
  */
61
61
  Object.defineProperty(exports, "__esModule", { value: true });
62
- exports.skillGraph = exports.SKILL_GRAPH_METADATA_KEY = exports.decideSkill = exports.formatCheckup = void 0;
62
+ exports.skillGraph = exports.SKILL_GRAPH_DEFERRED_CONTRACT_KEY = exports.SKILL_GRAPH_METADATA_KEY = exports.decideSkill = exports.formatCheckup = void 0;
63
63
  const footprintjs_1 = require("footprintjs");
64
+ const types_js_1 = require("./types.js");
64
65
  const entryScorer_js_1 = require("./entryScorer.js");
65
66
  const skillGraphCheckup_js_1 = require("./skillGraphCheckup.js");
66
67
  const skillContract_js_1 = require("./skillContract.js");
68
+ const skillMatch_js_1 = require("./skillMatch.js");
67
69
  var skillGraphCheckup_js_2 = require("./skillGraphCheckup.js");
68
70
  Object.defineProperty(exports, "formatCheckup", { enumerable: true, get: function () { return skillGraphCheckup_js_2.formatCheckup; } });
69
71
  /** Build a decision node. Leaves are skills (an `Injection`); internal nodes are
@@ -78,6 +80,17 @@ function isDecisionNode(n) {
78
80
  }
79
81
  /** The metadata key carrying a skill's routing provenance. */
80
82
  exports.SKILL_GRAPH_METADATA_KEY = 'skillGraph';
83
+ /**
84
+ * The metadata key carrying the {@link DeferredBodyContract} note on each COMPILED
85
+ * skill. The graph-level `graph.deferredBodyContract` note names the deferral, but
86
+ * skills reach an agent through more than one door — `.skillGraph(graph)` sees the
87
+ * graph, `.skills({ list: () => graph.skills })` sees only the skills — so the note
88
+ * also rides each skill's own metadata. Agent build collects it from the final
89
+ * injection list, whichever door the skills came through, and runs the deferred
90
+ * checks exactly once (skills found by BOTH the metadata and the graph note are
91
+ * deduped by id).
92
+ */
93
+ exports.SKILL_GRAPH_DEFERRED_CONTRACT_KEY = 'skillGraphDeferredBodyContract';
81
94
  /** Mermaid node ids must be identifier-safe; keep the original id as the label. */
82
95
  function nodeId(id) {
83
96
  return 'n_' + id.replace(/[^A-Za-z0-9_]/g, '_');
@@ -133,7 +146,25 @@ function skillGraph(config) {
133
146
  const builder = {
134
147
  entry(skill, opts) {
135
148
  const id = remember(skill);
136
- entries.push({ id, when: opts?.when, label: opts?.label });
149
+ // Exactly one condition per entry: `match` (data) or `when` (code). Both set
150
+ // is a contradiction — which one starts the turn? — refused rather than
151
+ // silently ANDed or ORed. (Neither set stays the `always` entry it has
152
+ // always been; the rules form additionally refuses "neither", because a
153
+ // rule exists to be conditional — see the config translation below.)
154
+ if (opts?.when !== undefined && opts?.match !== undefined) {
155
+ throw new Error(`skillGraph: entry "${id}" sets both \`match\` and \`when\` — an entry takes ` +
156
+ `exactly one condition. Use \`match\` (a RegExp or { keywords: [...] } over the ` +
157
+ `user message — comparable and drawable) OR \`when\` (a predicate over the ` +
158
+ `iteration context). To combine a pattern with extra logic, fold the pattern ` +
159
+ `into your \`when\` predicate.`);
160
+ }
161
+ const compiled = opts?.match !== undefined ? (0, skillMatch_js_1.compileMatch)(opts.match, `entry "${id}"`) : undefined;
162
+ entries.push({
163
+ id,
164
+ when: compiled ? compiled.predicate : opts?.when,
165
+ ...(compiled && { match: compiled.data }),
166
+ label: opts?.label,
167
+ });
137
168
  return builder;
138
169
  },
139
170
  route(from, to, opts) {
@@ -182,6 +213,14 @@ function skillGraph(config) {
182
213
  throw new Error('skillGraph: .entryBy()/.entryByRelevance() is for flat entry/route graphs; a ' +
183
214
  '.tree() already routes by predicate (the scorer would be ignored).');
184
215
  }
216
+ // One dial, one home: a tree declares tool scoping on `.tree(root, { scopeTools })`
217
+ // (object form: the tree arm's own field). Accepting it here too would let the two
218
+ // homes disagree, and only one of them would win — silently.
219
+ if (treeRoot && opts.scopeTools !== undefined) {
220
+ throw new Error('skillGraph: `scopeTools` on build() is the FLAT arm\'s dial. This graph is a ' +
221
+ '.tree(), which declares tool scoping on .tree(root, { scopeTools }) (object ' +
222
+ 'form: the tree arm\'s own `scopeTools` field) — set it there instead.');
223
+ }
185
224
  // A tree and the flat entry/route wiring are two ways to declare the same
186
225
  // thing, and only ONE of them compiles: the tree branch below never reads
187
226
  // `entries` or `routes`. Until 8.4.0 that was silent — the tree won and every
@@ -212,7 +251,16 @@ function skillGraph(config) {
212
251
  const checkup = (options = {}) => {
213
252
  const wiring = (0, skillGraphCheckup_js_1.checkupGraph)({
214
253
  skillIds: new Set(skillsById.keys()),
215
- entries: entries.map((e) => ({ id: e.id, conditional: e.when !== undefined })),
254
+ // `conditional` counts a data `match` exactly like a `when` (it compiled
255
+ // to one), so a rule-router built from matchers is never read as a
256
+ // fan-out. The match DATA rides along so the pairwise rule checks
257
+ // (`overlapping-rules` / `rules-shadowed-by-order`) have something they
258
+ // can honestly compare.
259
+ entries: entries.map((e) => ({
260
+ id: e.id,
261
+ conditional: e.when !== undefined,
262
+ ...(e.match && { match: e.match }),
263
+ })),
216
264
  routes: routes.map((r) => ({
217
265
  fromId: r.fromId,
218
266
  toId: r.toId,
@@ -316,17 +364,39 @@ function skillGraph(config) {
316
364
  }
317
365
  if (entryScorer)
318
366
  scoreEntries = makeScoreEntries(entries, skillsById, entryScorer);
367
+ // `scopeTools: true` (flat) — the WIRED skills, i.e. everything an entry or a
368
+ // route mentions. Stamped exactly as the tree stamps its leaves: a graph-level
369
+ // DEFAULT (`existingAuto ?? …`), never an override of a skill's own declaration.
370
+ // A listed-but-unwired skill stays additive — the graph does not route it, so
371
+ // the graph does not scope it (it remains an OPEN, read_skill-reachable skill).
372
+ // Default `false` keeps today's bytes; 10.0.0 flips the default.
373
+ const scopedIds = opts.scopeTools === true
374
+ ? new Set([...entries.map((e) => e.id), ...routes.flatMap((r) => [r.fromId, r.toId])])
375
+ : undefined;
319
376
  for (const [id, skill] of skillsById) {
320
377
  const trigger = deriveTrigger(id, skill, entries, routes, nextSkillForTriggers, entryScorer !== undefined || llmReadEntry);
321
378
  const routing = routingFor(id, entries, routes);
379
+ const existingAuto = skill.metadata
380
+ ?.autoActivate;
381
+ const autoActivate = existingAuto ?? (scopedIds?.has(id) ? 'currentSkill' : undefined);
322
382
  skills.push({
323
383
  ...skill,
324
384
  ...(trigger && { trigger }),
325
- metadata: { ...skill.metadata, [exports.SKILL_GRAPH_METADATA_KEY]: routing },
385
+ metadata: {
386
+ ...skill.metadata,
387
+ [exports.SKILL_GRAPH_METADATA_KEY]: routing,
388
+ ...(autoActivate && { autoActivate }),
389
+ },
326
390
  });
327
391
  nodes.push({ id, kind: 'skill', label: id });
328
392
  }
329
- edges.push(...entries.map((e) => ({ from: null, to: e.id, kind: 'entry', label: e.label })), ...routes.map((r) => ({
393
+ edges.push(...entries.map((e) => ({
394
+ from: null,
395
+ to: e.id,
396
+ kind: 'entry',
397
+ label: e.label,
398
+ ...(e.match && { match: e.match }),
399
+ })), ...routes.map((r) => ({
330
400
  from: r.fromId,
331
401
  to: r.toId,
332
402
  kind: r.onToolReturn ? 'on-tool-return' : r.when ? 'predicate' : 'model',
@@ -342,20 +412,53 @@ function skillGraph(config) {
342
412
  // surfaced as a run that entered no skill. An explicit `check: 'warn'` still
343
413
  // never throws, so the opt-down survives with its name intact.
344
414
  const check = opts.check ?? 'throw';
415
+ // Without `knownTools`, the body-contract checks (`body-foreign-tool` /
416
+ // `body-unknown-tool`) cannot tell a typo from a baseline tool the agent
417
+ // registers later — the graph builds before `.tool()` runs. So this PASS
418
+ // defers them (reports nothing it cannot prove) and leaves a
419
+ // `deferredBodyContract` note in TWO places that say the same thing: on the
420
+ // graph, and on each compiled skill's metadata — because skills reach an
421
+ // agent through more than one door (`.skillGraph(graph)` sees the graph;
422
+ // `.skills({ list: () => graph.skills })` sees only the skills). Agent build
423
+ // collects the note from its final injection list and runs the checks exactly
424
+ // once against the full tool registry, whichever door was used. With
425
+ // `knownTools` given, the author supplied the full picture and the checks run
426
+ // right here, as always — no note anywhere, and the agent never re-runs them
427
+ // (one problem, one report). `graph.checkup()` is untouched either way: the
428
+ // explicit lint call always runs every check over what it can see.
429
+ const deferBodyContract = check !== 'off' && opts.knownTools === undefined;
430
+ const deferredNote = deferBodyContract
431
+ ? { mode: check }
432
+ : undefined;
345
433
  if (check !== 'off') {
346
434
  const result = checkup({ ...(opts.knownTools && { knownTools: opts.knownTools }) });
347
- if (check === 'throw' && !result.ok) {
348
- throw new Error(`skillGraph: build-time check-up failed:\n${(0, skillGraphCheckup_js_1.formatCheckup)(result)}`);
435
+ const reported = deferBodyContract
436
+ ? {
437
+ ok: result.ok, // body-contract problems are warnings — they never decide `ok`
438
+ problems: result.problems.filter((p) => p.code !== 'body-foreign-tool' && p.code !== 'body-unknown-tool'),
439
+ }
440
+ : result;
441
+ if (check === 'throw' && !reported.ok) {
442
+ throw new Error(`skillGraph: build-time check-up failed:\n${(0, skillGraphCheckup_js_1.formatCheckup)(reported)}`);
349
443
  }
350
- if (result.problems.length > 0 && (0, footprintjs_1.isDevMode)()) {
444
+ if (reported.problems.length > 0 && (0, footprintjs_1.isDevMode)()) {
351
445
  // eslint-disable-next-line no-console
352
- console.warn(`skillGraph: build-time check-up found problems:\n${(0, skillGraphCheckup_js_1.formatCheckup)(result)}`);
446
+ console.warn(`skillGraph: build-time check-up found problems:\n${(0, skillGraphCheckup_js_1.formatCheckup)(reported)}`);
353
447
  }
354
448
  }
355
449
  return {
356
- skills,
450
+ // Deferring? The note rides each skill too (dedup at agent build is by id,
451
+ // so the double stamp can never double-report). No deferral → the exact
452
+ // same objects as always: zero cost when unused.
453
+ skills: deferredNote
454
+ ? skills.map((s) => ({
455
+ ...s,
456
+ metadata: { ...s.metadata, [exports.SKILL_GRAPH_DEFERRED_CONTRACT_KEY]: deferredNote },
457
+ }))
458
+ : skills,
357
459
  edges,
358
460
  nodes,
461
+ ...(deferredNote && { deferredBodyContract: deferredNote }),
359
462
  toMermaid: () => renderMermaid(nodes, edges),
360
463
  // `nextSkill` is the `.to` PROJECTION of the one resolver, not a second
361
464
  // implementation — the two cannot answer differently.
@@ -412,9 +515,51 @@ function skillGraph(config) {
412
515
  builder.entry(resolve(start));
413
516
  else if ('use' in start)
414
517
  builder.entry(resolve(start.use));
415
- else if ('rules' in start)
416
- for (const r of start.rules)
417
- builder.entry(resolve(r.use), { when: r.when });
518
+ else if ('rules' in start) {
519
+ // (a) rule-id-exists — validate EVERY rule's target before compiling any of
520
+ // them, so the refusal lists every bad id at once (not just the first) and
521
+ // names what IS allowed. An unknown id cannot compile into an entry at all,
522
+ // so this refuses under every `check` mode — a rule silently dropped under
523
+ // `check: 'warn'` would be the accepted-and-silently-wrong build.
524
+ const missing = start.rules
525
+ .map((r, i) => ({ use: r.use, i }))
526
+ .filter(({ use }) => !skillsById.has(use));
527
+ if (missing.length > 0) {
528
+ const known = [...skillsById.keys()].map((k) => `"${k}"`).join(', ');
529
+ const problems = missing.map(({ use, i }) => ({
530
+ kind: 'error',
531
+ code: 'rule-id-exists',
532
+ message: `start.rules[${i}] routes to skill "${use}", which is not in skills[]. ` +
533
+ `Known skill ids: ${known || '(none)'}. Fix the rule's \`use\`, or add the ` +
534
+ `skill to skills[].`,
535
+ skill: use,
536
+ }));
537
+ throw new Error(`skillGraph: build-time check-up failed:\n${(0, skillGraphCheckup_js_1.formatCheckup)({ ok: false, problems })}`);
538
+ }
539
+ for (const [i, r] of start.rules.entries()) {
540
+ // Exactly one of `match`/`when` per rule. The type already says so
541
+ // (`SkillStartRule` is a union with `never` on the other arm); this is the
542
+ // runtime half, for JavaScript callers and configs assembled through `any`.
543
+ // "Neither" is refused too — a rule exists to be conditional; the
544
+ // unconditional start is `start: 'id'` / `{ use }`, and saying so beats
545
+ // compiling a rule that would swallow every turn.
546
+ const use = r.use; // read before the narrowing checks — TS collapses the
547
+ // union to `never` inside the refusal branch (the type says it can't happen;
548
+ // this is the runtime half for callers the compiler never saw).
549
+ const both = r.when !== undefined && r.match !== undefined;
550
+ const neither = r.when === undefined && r.match === undefined;
551
+ if (both || neither) {
552
+ throw new Error(`skillGraph: start.rules[${i}] (use: "${use}") ${both ? 'sets both `match` and `when`' : 'sets neither `match` nor `when`'} — a rule takes exactly one condition. Say when it applies with \`match\` ` +
553
+ `(a RegExp or { keywords: [...] } tested against the user message) or ` +
554
+ `\`when\` (a predicate over the iteration context). For an unconditional ` +
555
+ `start, use \`start: '${use}'\` instead of a rule.`);
556
+ }
557
+ builder.entry(resolve(r.use), {
558
+ ...(r.when && { when: r.when }),
559
+ ...(r.match !== undefined && { match: r.match }),
560
+ });
561
+ }
562
+ }
418
563
  else {
419
564
  for (const id of start.entries)
420
565
  builder.entry(resolve(id));
@@ -437,6 +582,11 @@ function skillGraph(config) {
437
582
  return builder.build({
438
583
  check: config.check ?? 'throw',
439
584
  ...(config.knownTools && { knownTools: config.knownTools }),
585
+ // The FLAT arm's tool-scoping dial rides through to the one compiler below.
586
+ // (The tree arm's `scopeTools` was already handed to `.tree()` above — passing
587
+ // it here too would trip build()'s one-dial-one-home refusal.)
588
+ ...(config.tree === undefined &&
589
+ config.scopeTools !== undefined && { scopeTools: config.scopeTools }),
440
590
  });
441
591
  }
442
592
  return builder;
@@ -493,16 +643,15 @@ function makeScoreEntries(entries, skillsById, scorer) {
493
643
  return scorer.score({ userMessage: ctx.userMessage, candidates }, signal);
494
644
  };
495
645
  }
496
- /** Does a single route edge fire for this context? Reads the previous
497
- * iteration's tool result; `onToolReturn` matches the tool NAME, `when` runs
498
- * the predicate over the result. No match (and no tool result) → false. */
499
- function routeMatches(r, ctx) {
500
- const ltr = ctx.lastToolResult;
501
- if (!ltr)
502
- return false;
646
+ /** Does a single route edge fire for ONE tool result of the batch?
647
+ * `onToolReturn` matches the tool NAME, `when` runs the predicate over the
648
+ * result (same `{ toolName, result }` shape it has always received). The
649
+ * caller walks the batch in call order (9.16.0) — before that, only
650
+ * `ctx.lastToolResult` was ever offered here. */
651
+ function routeMatches(r, res) {
503
652
  if (r.onToolReturn)
504
- return toolMatcher(r.onToolReturn)(ltr.toolName);
505
- return r.when ? r.when(ltr) : false;
653
+ return toolMatcher(r.onToolReturn)(res.toolName);
654
+ return r.when ? r.when(res) : false;
506
655
  }
507
656
  /**
508
657
  * The cursor resolver (the keystone). Pure + deterministic. Given the iteration
@@ -512,7 +661,10 @@ function routeMatches(r, ctx) {
512
661
  * (an `always`-entry — no `when` — matches unconditionally), else the entry
513
662
  * the MODEL picked with `read_skill` (see "the model's pick" below);
514
663
  * • a `from`-gated route (`fromId === currentSkillId`) whose predicate matches
515
- * `lastToolResult`, first by declaration order → its target (the transition);
664
+ * a result of the iteration's tool batch — results in call order, edges per
665
+ * result in declaration order; the first result with a match wins, later
666
+ * results matching OTHER targets are reported as `conflict` (9.16.0) →
667
+ * its target (the transition);
516
668
  * • else the model's pick, when the read_skill gate accepted one this turn;
517
669
  * • otherwise the current cursor unchanged (sticky stay).
518
670
  *
@@ -576,22 +728,59 @@ function makeResolveCursor(entries, routes, llmReadEntry = false) {
576
728
  }
577
729
  return { by: 'none' };
578
730
  }
579
- // D1 — transition: first from-gated deterministic edge that fires. Wins over
580
- // a same-turn model pick (the author's declared edge is never overridden) —
581
- // INCLUDING when both name the same target, which is why the cause is decided
582
- // here and not inferred downstream from the destination.
583
- for (const r of routes) {
584
- if (r.fromId !== cur)
585
- continue;
586
- if (!r.when && !r.onToolReturn)
587
- continue; // model edges don't auto-fire
588
- try {
589
- if (routeMatches(r, ctx))
590
- return { ...from, to: r.toId, by: 'route' };
591
- }
592
- catch (err) {
593
- warnMatcherThrew(`route ${r.fromId}→${r.toId}`, err);
731
+ // D1 — transition: the whole tool batch, in CALL order (9.16.0). Per
732
+ // result, the first from-gated deterministic edge in DECLARATION order
733
+ // that fires names that result's target; the first RESULT with a target
734
+ // wins the cursor. Two orders, two jobs: declaration order is the
735
+ // author's tiebreak when one result could match several edges (the D1
736
+ // law, unchanged), call order is the run's tiebreak across results —
737
+ // the tool that returned first routed first. Before 9.16.0 only the
738
+ // LAST call of a batch was consulted, so identical batches routed
739
+ // differently depending on call order (the 9.15.0 L1 probe). A later
740
+ // result matching a DIFFERENT target is suppressed AND reported on the
741
+ // move (`conflict`), never silently dropped; a batch of one is
742
+ // byte-identical to the old singular walk. D1 still wins over a
743
+ // same-turn model pick — INCLUDING when both name the same target,
744
+ // which is why the cause is decided here and not inferred downstream
745
+ // from the destination.
746
+ let winner;
747
+ let losers;
748
+ for (const res of (0, types_js_1.toolResultsOf)(ctx)) {
749
+ let target;
750
+ for (const r of routes) {
751
+ if (r.fromId !== cur)
752
+ continue;
753
+ if (!r.when && !r.onToolReturn)
754
+ continue; // model edges don't auto-fire
755
+ try {
756
+ if (routeMatches(r, res)) {
757
+ target = r.toId;
758
+ break;
759
+ }
760
+ }
761
+ catch (err) {
762
+ warnMatcherThrew(`route ${r.fromId}→${r.toId}`, err);
763
+ }
594
764
  }
765
+ if (target === undefined)
766
+ continue;
767
+ const outcome = {
768
+ ...(res.toolCallId !== undefined && { toolCallId: res.toolCallId }),
769
+ toolName: res.toolName,
770
+ target,
771
+ };
772
+ if (winner === undefined)
773
+ winner = outcome;
774
+ else if (target !== winner.target)
775
+ (losers ??= []).push(outcome);
776
+ }
777
+ if (winner !== undefined) {
778
+ return {
779
+ ...from,
780
+ to: winner.target,
781
+ by: 'route',
782
+ ...(losers !== undefined && { conflict: { winner, losers } }),
783
+ };
595
784
  }
596
785
  // D2 — the validated volunteer: no declared edge fired, so the model's own
597
786
  // (already gated) pick moves the cursor. A pick of the CURRENT skill is a
@@ -911,7 +1100,11 @@ function attachExactlyOneLeafMonitor(skills) {
911
1100
  function routingFor(id, entries, routes) {
912
1101
  const entry = entries.find((e) => e.id === id);
913
1102
  if (entry)
914
- return { via: 'entry', ...(entry.label && { label: entry.label }) };
1103
+ return {
1104
+ via: 'entry',
1105
+ ...(entry.label && { label: entry.label }),
1106
+ ...(entry.match && { match: entry.match }),
1107
+ };
915
1108
  const incoming = routes.filter((r) => r.toId === id && (r.when || r.onToolReturn));
916
1109
  const first = incoming[0];
917
1110
  if (first) {
@@ -936,7 +1129,10 @@ function renderMermaid(nodes, edges) {
936
1129
  for (const e of edges) {
937
1130
  const from = e.from === null ? '__start__' : ref(e.from);
938
1131
  const arrow = e.kind === 'model' ? '-.->' : '-->'; // model edges dashed
939
- const label = e.label ? `|${e.label}|` : '';
1132
+ // An explicit label wins unchanged; a data matcher captions its entry edge
1133
+ // (that is half the point of declaring the rule as data — it can be drawn).
1134
+ const caption = e.label ?? (e.match ? (0, skillMatch_js_1.mermaidMatchCaption)(e.match) : undefined);
1135
+ const label = caption ? `|${caption}|` : '';
940
1136
  lines.push(` ${from} ${arrow}${label} ${ref(e.to)}`);
941
1137
  }
942
1138
  return lines.join('\n');