@jangts/agent-core 0.3.6 → 0.5.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 (214) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +5 -5
  3. package/development/docs/README.en.md +2 -2
  4. package/development/docs/acceptance.en.md +2 -2
  5. package/development/docs/agent-development.en.md +1 -1
  6. package/development/docs/api/README.en.md +4 -2
  7. package/development/docs/api/README.md +2 -0
  8. package/development/docs/api/configuration-in-workflow.en.md +403 -0
  9. package/development/docs/api/configuration-in-workflow.md +403 -0
  10. package/development/docs/api/core.en.md +7 -7
  11. package/development/docs/api/core.md +6 -6
  12. package/development/docs/api/decorators.en.md +20 -0
  13. package/development/docs/api/decorators.md +20 -0
  14. package/development/docs/api/examples.en.md +1 -1
  15. package/development/docs/api/examples.md +1 -1
  16. package/development/docs/api/kits.en.md +22 -4
  17. package/development/docs/api/kits.md +22 -4
  18. package/development/docs/api/knowledge-answering.en.md +4 -1
  19. package/development/docs/api/knowledge-answering.md +4 -1
  20. package/development/docs/api/mock.en.md +2 -0
  21. package/development/docs/api/mock.md +2 -0
  22. package/development/docs/api/reference.en.md +508 -410
  23. package/development/docs/api/reference.md +508 -410
  24. package/development/docs/api/support.en.md +55 -9
  25. package/development/docs/api/support.md +53 -7
  26. package/development/docs/api/types.en.md +35 -1
  27. package/development/docs/api/types.md +43 -0
  28. package/development/docs/basic-usage.en.md +1 -1
  29. package/development/docs/basic-usage.md +1 -1
  30. package/development/docs/create-app.en.md +1 -1
  31. package/development/docs/examples/registration/associated.mjs +8 -0
  32. package/development/docs/examples/registration/independent.mjs +20 -0
  33. package/development/docs/examples/registration/protocol.mjs +48 -0
  34. package/development/docs/examples/registration/run.mjs +47 -0
  35. package/development/docs/examples/registration/tools.mjs +75 -0
  36. package/development/docs/examples/registration/workflow.mjs +109 -0
  37. package/development/docs/flows/knowledge-maintenance.en.md +1 -1
  38. package/development/docs/materials.en.md +1 -1
  39. package/development/docs/mechanisms.en.md +2 -2
  40. package/development/docs/mechanisms.md +1 -1
  41. package/development/docs/operations.en.md +1 -1
  42. package/development/docs/records.en.md +1 -1
  43. package/development/docs/registration.en.md +4 -4
  44. package/development/docs/registration.md +2 -2
  45. package/development/skills/agent-core-app/SKILL.md +4 -4
  46. package/development/skills/agent-core-app/templates/node-ts/README.md.tpl +1 -1
  47. package/development/skills/agent-core-app/templates/node-ts/src/catalog.ts.tpl +2 -2
  48. package/development/skills/agent-core-app/templates/node-ts/test/app.test.mjs.tpl +1 -1
  49. package/dist/cjs/capabilities/index.js +5 -12
  50. package/dist/cjs/capabilities/model.js +56 -12
  51. package/dist/cjs/capabilities/tool-conditions.js +65 -0
  52. package/dist/cjs/capabilities/tool-runtime.js +10 -3
  53. package/dist/cjs/context/audit.js +5 -8
  54. package/dist/cjs/context/index.js +3 -4
  55. package/dist/cjs/index.js +21 -17
  56. package/dist/cjs/kits/answering/tasks.js +5 -5
  57. package/dist/cjs/kits/domains/catalog.js +113 -0
  58. package/dist/cjs/kits/domains/index.js +20 -0
  59. package/dist/cjs/kits/{intents → domains}/registry.js +77 -31
  60. package/dist/cjs/kits/{intents → domains}/scopes.js +21 -21
  61. package/dist/cjs/kits/index.js +14 -13
  62. package/dist/cjs/kits/presentation/formats.js +110 -0
  63. package/dist/cjs/kits/presentation/index.js +10 -5
  64. package/dist/cjs/kits/reflection.js +2 -2
  65. package/dist/cjs/kits/text-blocks.js +101 -0
  66. package/dist/cjs/kits/tools.js +246 -158
  67. package/dist/cjs/model/index.js +2 -6
  68. package/dist/cjs/model/interactions/calls.js +61 -0
  69. package/dist/cjs/model/interactions/json-object.js +11 -11
  70. package/dist/cjs/model/interactions/native-functions.js +17 -23
  71. package/dist/cjs/model/message-diagnostics.js +13 -3
  72. package/dist/cjs/model/options.js +2 -0
  73. package/dist/cjs/model/preparation.js +39 -6
  74. package/dist/cjs/model/runner.js +14 -0
  75. package/dist/cjs/model/selection-transport.js +4 -4
  76. package/dist/cjs/model/token-budget.js +3 -0
  77. package/dist/cjs/model/transports.js +3 -2
  78. package/dist/cjs/structs/index.js +7 -6
  79. package/dist/cjs/structs/kits/presentation/formats.js +2 -0
  80. package/dist/cjs/structs/kits/text-blocks.js +11 -0
  81. package/dist/cjs/structs/model/session.js +11 -0
  82. package/dist/cjs/structs/support/associations.js +2 -0
  83. package/dist/cjs/support/associations.js +79 -0
  84. package/dist/cjs/support/context/snapshot.js +11 -1
  85. package/dist/cjs/support/decorators/index.js +45 -3
  86. package/dist/cjs/support/index.js +1 -0
  87. package/dist/cjs/support/model/capabilities.js +35 -5
  88. package/dist/cjs/support/model/chat-completions.js +11 -2
  89. package/dist/cjs/support/model/completion/calls.js +10 -56
  90. package/dist/cjs/support/model/preview-rules.js +11 -11
  91. package/dist/cjs/support/model/reply-preview.js +3 -3
  92. package/dist/cjs/support/model/session.js +152 -0
  93. package/dist/cjs/support/model/stage-transport.js +8 -8
  94. package/dist/cjs/support/model/transports.js +5 -2
  95. package/dist/cjs/support/tools.js +42 -17
  96. package/dist/esm/capabilities/index.d.ts +5 -10
  97. package/dist/esm/capabilities/index.js +5 -9
  98. package/dist/esm/capabilities/model.d.ts +17 -13
  99. package/dist/esm/capabilities/model.js +54 -10
  100. package/dist/esm/capabilities/tool-conditions.d.ts +9 -0
  101. package/dist/esm/capabilities/tool-conditions.js +59 -0
  102. package/dist/esm/capabilities/tool-runtime.js +10 -3
  103. package/dist/esm/capabilities/tools.d.ts +3 -3
  104. package/dist/esm/context/audit.js +5 -8
  105. package/dist/esm/context/index.d.ts +3 -4
  106. package/dist/esm/context/index.js +3 -4
  107. package/dist/esm/index.d.ts +19 -13
  108. package/dist/esm/index.js +18 -15
  109. package/dist/esm/kits/answering/tasks.d.ts +6 -6
  110. package/dist/esm/kits/answering/tasks.js +5 -5
  111. package/dist/esm/kits/domains/catalog.d.ts +42 -0
  112. package/dist/esm/kits/domains/catalog.js +110 -0
  113. package/dist/esm/kits/domains/index.d.ts +4 -0
  114. package/dist/esm/kits/domains/index.js +4 -0
  115. package/dist/esm/kits/domains/registry.d.ts +100 -0
  116. package/dist/esm/kits/{intents → domains}/registry.js +77 -31
  117. package/dist/esm/kits/{intents → domains}/scopes.d.ts +18 -18
  118. package/dist/esm/kits/{intents → domains}/scopes.js +21 -21
  119. package/dist/esm/kits/index.d.ts +6 -5
  120. package/dist/esm/kits/index.js +6 -5
  121. package/dist/esm/kits/presentation/formats.d.ts +42 -0
  122. package/dist/esm/kits/presentation/formats.js +105 -0
  123. package/dist/esm/kits/presentation/index.d.ts +14 -5
  124. package/dist/esm/kits/presentation/index.js +10 -5
  125. package/dist/esm/kits/reflection.js +3 -3
  126. package/dist/esm/kits/text-blocks.d.ts +24 -0
  127. package/dist/esm/kits/text-blocks.js +97 -0
  128. package/dist/esm/kits/tools.d.ts +69 -52
  129. package/dist/esm/kits/tools.js +246 -158
  130. package/dist/esm/model/index.d.ts +2 -6
  131. package/dist/esm/model/index.js +2 -6
  132. package/dist/esm/model/interactions/calls.d.ts +40 -0
  133. package/dist/esm/model/interactions/calls.js +58 -0
  134. package/dist/esm/model/interactions/json-object.js +11 -11
  135. package/dist/esm/model/interactions/native-functions.js +17 -23
  136. package/dist/esm/model/message-diagnostics.d.ts +6 -5
  137. package/dist/esm/model/message-diagnostics.js +13 -3
  138. package/dist/esm/model/options.js +2 -0
  139. package/dist/esm/model/preparation.js +39 -6
  140. package/dist/esm/model/runner.js +14 -0
  141. package/dist/esm/model/selection-transport.js +4 -4
  142. package/dist/esm/model/token-budget.js +3 -0
  143. package/dist/esm/model/transports.d.ts +2 -1
  144. package/dist/esm/model/transports.js +3 -2
  145. package/dist/esm/structs/capabilities/invocation.d.ts +1 -1
  146. package/dist/esm/structs/capabilities/model.d.ts +42 -11
  147. package/dist/esm/structs/capabilities/tools.d.ts +9 -4
  148. package/dist/esm/structs/common.d.ts +1 -1
  149. package/dist/esm/structs/index.d.ts +7 -6
  150. package/dist/esm/structs/index.js +7 -6
  151. package/dist/esm/structs/kits/answering/tasks.d.ts +2 -2
  152. package/dist/esm/structs/kits/{intents → domains}/catalog.d.ts +20 -20
  153. package/dist/esm/structs/kits/{intents → domains}/registry.d.ts +24 -21
  154. package/dist/esm/structs/kits/{intents → domains}/scopes.d.ts +9 -9
  155. package/dist/esm/structs/kits/presentation/formats.d.ts +42 -0
  156. package/dist/esm/structs/kits/presentation/formats.js +1 -0
  157. package/dist/esm/structs/kits/text-blocks.d.ts +25 -0
  158. package/dist/esm/structs/kits/text-blocks.js +10 -0
  159. package/dist/esm/structs/kits/tools.d.ts +82 -83
  160. package/dist/esm/structs/model/interactions.d.ts +18 -11
  161. package/dist/esm/structs/model/messages.d.ts +34 -2
  162. package/dist/esm/structs/model/runtime.d.ts +7 -0
  163. package/dist/esm/structs/model/session.d.ts +29 -0
  164. package/dist/esm/structs/model/session.js +10 -0
  165. package/dist/esm/structs/support/associations.d.ts +22 -0
  166. package/dist/esm/structs/support/associations.js +1 -0
  167. package/dist/esm/structs/support/model/interactions.d.ts +2 -2
  168. package/dist/esm/structs/support/model/messages.d.ts +6 -13
  169. package/dist/esm/structs/support/tools.d.ts +23 -23
  170. package/dist/esm/support/associations.d.ts +26 -0
  171. package/dist/esm/support/associations.js +76 -0
  172. package/dist/esm/support/context/snapshot.js +11 -1
  173. package/dist/esm/support/decorators/index.d.ts +13 -2
  174. package/dist/esm/support/decorators/index.js +40 -3
  175. package/dist/esm/support/decorators/pool.d.ts +6 -5
  176. package/dist/esm/support/decorators/types.d.ts +3 -1
  177. package/dist/esm/support/index.d.ts +1 -0
  178. package/dist/esm/support/index.js +1 -0
  179. package/dist/esm/support/model/capabilities.d.ts +8 -3
  180. package/dist/esm/support/model/capabilities.js +35 -5
  181. package/dist/esm/support/model/chat-completions.js +11 -2
  182. package/dist/esm/support/model/completion/calls.d.ts +6 -34
  183. package/dist/esm/support/model/completion/calls.js +8 -55
  184. package/dist/esm/support/model/index.d.ts +1 -1
  185. package/dist/esm/support/model/preview-rules.d.ts +13 -8
  186. package/dist/esm/support/model/preview-rules.js +11 -11
  187. package/dist/esm/support/model/reply-preview.d.ts +2 -2
  188. package/dist/esm/support/model/reply-preview.js +3 -3
  189. package/dist/esm/support/model/session.d.ts +61 -0
  190. package/dist/esm/support/model/session.js +148 -0
  191. package/dist/esm/support/model/stage-transport.d.ts +1 -1
  192. package/dist/esm/support/model/stage-transport.js +9 -9
  193. package/dist/esm/support/model/transports.d.ts +2 -0
  194. package/dist/esm/support/model/transports.js +2 -1
  195. package/dist/esm/support/tools.d.ts +11 -3
  196. package/dist/esm/support/tools.js +43 -18
  197. package/dist/esm/utils/catalog.d.ts +1 -1
  198. package/package.json +20 -6
  199. package/dist/cjs/execution/index.js +0 -30
  200. package/dist/cjs/kits/intents/catalog.js +0 -82
  201. package/dist/cjs/kits/intents/index.js +0 -35
  202. package/dist/esm/execution/index.d.ts +0 -11
  203. package/dist/esm/execution/index.js +0 -11
  204. package/dist/esm/kits/intents/catalog.d.ts +0 -39
  205. package/dist/esm/kits/intents/catalog.js +0 -79
  206. package/dist/esm/kits/intents/index.d.ts +0 -22
  207. package/dist/esm/kits/intents/index.js +0 -18
  208. package/dist/esm/kits/intents/registry.d.ts +0 -88
  209. /package/dist/cjs/structs/kits/{intents → domains}/catalog.js +0 -0
  210. /package/dist/cjs/structs/kits/{intents → domains}/registry.js +0 -0
  211. /package/dist/cjs/structs/kits/{intents → domains}/scopes.js +0 -0
  212. /package/dist/esm/structs/kits/{intents → domains}/catalog.js +0 -0
  213. /package/dist/esm/structs/kits/{intents → domains}/registry.js +0 -0
  214. /package/dist/esm/structs/kits/{intents → domains}/scopes.js +0 -0
@@ -2,13 +2,16 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Tools = exports.ToolLifecycleRegistry = void 0;
4
4
  /**
5
- * Tools Kit: a lifecycle registry for domain tools with declared invocation and contribution.
5
+ * Tools Kit: a lifecycle registry for App-declared tools.
6
6
  *
7
7
  * Place in the package: an optional App-side library for tools that need more than a plain
8
- * definition — availability, per-step protocols, output ownership, context fields, task rules
8
+ * definition — availability, one lifecycle hook set, output ownership, context fields, task rules
9
9
  * and delivery hooks. Actual invocation is delegated to Core (`ToolRegistry` and
10
10
  * `executeInvocation`); domain predicates, phase names and vocabulary stay App configuration.
11
11
  *
12
+ * A tool claims an invocation through `lifecycle.id` and, when a node dispatches by name, a
13
+ * delegate id through `delegate`. Both indexes live here, so dispatch is one lookup.
14
+ *
12
15
  * Exports: `ToolLifecycleRegistry` (also exported as `Tools`) and the tool lifecycle types.
13
16
  */
14
17
  const synchronous_js_1 = require("../utils/synchronous.js");
@@ -18,12 +21,34 @@ const tools_js_1 = require("../capabilities/tools.js");
18
21
  const invocation_js_2 = require("../capabilities/invocation.js");
19
22
  const protocol_registry_js_1 = require("../utils/protocol-registry.js");
20
23
  const registration_js_1 = require("../utils/registration.js");
24
+ const model_js_1 = require("../capabilities/model.js");
25
+ const tool_conditions_js_1 = require("../capabilities/tool-conditions.js");
26
+ /** Hooks that must finish with `undefined`; every other hook only rejects thenables. */
27
+ const assertionHooks = ['authorize', 'inspect', 'finalize'];
28
+ /** Every hook a lifecycle block may declare, in binding order. */
29
+ const lifecycleHooks = [
30
+ 'call',
31
+ 'authorize',
32
+ 'resume',
33
+ 'complete',
34
+ 'inspect',
35
+ 'finalize',
36
+ 'rules',
37
+ 'modelFields',
38
+ 'visibleText',
39
+ 'reviewDraft',
40
+ 'composition',
41
+ 'recoveryContext',
42
+ 'projectContext',
43
+ 'missingInteractionAllowed',
44
+ 'supportsGoal',
45
+ ];
21
46
  /** Pin contract methods and configuration without cloning the App services they use. */
22
47
  function snapshotLifecycleOptions(options) {
23
- const { receiver, execution, errors, registration, step, outputs, modelOutputs, unavailableStep, forbidden, contextFragment, intentIds, diagnostics, signal, journal, checkpoint, receipt, receiptError, } = options;
24
- if (typeof intentIds !== 'function')
25
- throw new TypeError('An intentIds provider is required');
26
- const { kinds, isValidId, audiences, effects, intentExists, protocolVersion, tags, invalidTool, invalidInvocation, } = registration;
48
+ const { receiver, execution, errors, registration, invocationId, outputs, modelOutputs, unavailableInvocation, forbidden, contextFragment, goalIds, diagnostics, signal, journal, checkpoint, receipt, receiptError, } = options;
49
+ if (typeof goalIds !== 'function')
50
+ throw new TypeError('An goalIds provider is required');
51
+ const { kinds, isValidId, audiences, effects, goalExists, protocolVersion, tags, invalidTool, invalidInvocation, } = registration;
27
52
  return Object.freeze({
28
53
  receiver: receiver?.bind(options),
29
54
  execution: execution && Object.freeze({ signal: execution.signal, diagnostics: execution.diagnostics }),
@@ -37,7 +62,7 @@ function snapshotLifecycleOptions(options) {
37
62
  audiences: Object.freeze([...audiences]),
38
63
  effects: Object.freeze([...effects]),
39
64
  isValidId: isValidId.bind(registration),
40
- intentExists: intentExists.bind(registration),
65
+ goalExists: goalExists.bind(registration),
41
66
  protocolVersion,
42
67
  tags: tags && Object.freeze([...tags]),
43
68
  invalidTool: typeof invalidTool === 'function' ? invalidTool.bind(registration) : invalidTool,
@@ -45,13 +70,13 @@ function snapshotLifecycleOptions(options) {
45
70
  ? invalidInvocation.bind(registration)
46
71
  : invalidInvocation,
47
72
  }),
48
- step: step.bind(options),
73
+ invocationId: invocationId.bind(options),
49
74
  outputs: outputs.bind(options),
50
75
  modelOutputs: modelOutputs?.bind(options),
51
- unavailableStep: unavailableStep?.bind(options),
76
+ unavailableInvocation: unavailableInvocation?.bind(options),
52
77
  forbidden: forbidden?.bind(options),
53
78
  contextFragment: contextFragment.bind(options),
54
- intentIds: intentIds.bind(options),
79
+ goalIds: goalIds.bind(options),
55
80
  diagnostics: diagnostics?.bind(options),
56
81
  signal: signal?.bind(options),
57
82
  journal: journal?.bind(options),
@@ -60,18 +85,38 @@ function snapshotLifecycleOptions(options) {
60
85
  receiptError: receiptError?.bind(options),
61
86
  });
62
87
  }
88
+ /** Bind every declared hook to its owner and freeze the result. */
89
+ function bindLifecycle(value) {
90
+ return Object.freeze({
91
+ ...(0, registration_js_1.registrationData)(value, [...lifecycleHooks, 'id', 'protocol']),
92
+ id: value.id,
93
+ protocol: value.protocol,
94
+ ...Object.fromEntries(lifecycleHooks.map((key) => {
95
+ const handler = value[key];
96
+ return [
97
+ key,
98
+ typeof handler === 'function'
99
+ ? (...args) => (assertionHooks.includes(key) ? synchronous_js_1.assertion : synchronous_js_1.synchronous)(handler.apply(value, args), `Tool lifecycle ${key} must finish synchronously with the declared result`)
100
+ : handler,
101
+ ];
102
+ })),
103
+ });
104
+ }
63
105
  /**
64
- * Registry of tools whose lifecycle, protocols and contributions are declared by the App.
106
+ * Registry of tools whose lifecycle, protocols and context contributions the App declares.
65
107
  *
66
108
  * Registration is only possible before `seal()`; afterwards the catalog is read-only, so a run
67
- * cannot change which tools exist. Every contribution hook is optional and is applied in
68
- * registration order, which is why `registerAll` preserves iteration order.
109
+ * cannot change which tools exist. Every hook is optional and is applied in registration order,
110
+ * which is why `registerAll` preserves iteration order.
69
111
  */
70
112
  class ToolLifecycleRegistry {
71
113
  tools;
72
114
  registered = new Map();
73
115
  sources = new Map();
116
+ /** Tools that own an invocation id; dispatch by decision next reads this index. */
74
117
  invocations = new Map();
118
+ /** Tools that answer a delegate id; dispatch by node reads this index. */
119
+ delegateIndex = new Map();
75
120
  normalizer;
76
121
  finalizer;
77
122
  sealed = false;
@@ -82,7 +127,7 @@ class ToolLifecycleRegistry {
82
127
  /**
83
128
  * @param options - Receiver, execution context, error wording, registration policy, protocol
84
129
  * providers and optional journal/receipt ports; contract methods are bound to their owner.
85
- * @throws TypeError When no `intentIds` provider is supplied.
130
+ * @throws TypeError When no `goalIds` provider is supplied.
86
131
  */
87
132
  constructor(options) {
88
133
  this.options = snapshotLifecycleOptions(options);
@@ -117,17 +162,31 @@ class ToolLifecycleRegistry {
117
162
  * hooks must return `void` synchronously, while the remaining handlers are checked for
118
163
  * accidental thenables only.
119
164
  *
120
- * @param input - Tool definition with identity, audience, effect and its handlers.
165
+ * @param input - Tool definition with identity, audience, effect and its hooks.
121
166
  * @returns This registry, for chaining.
122
167
  * @throws A configured registration error for a sealed registry, invalid identity,
123
- * description, audience, effect, intent ids, missing handlers, duplicate tool id,
124
- * incomplete invocation contract, protocol version mismatch or duplicate invocation step.
168
+ * description, audience, effect, goal ids, missing handlers, duplicate tool id,
169
+ * duplicate delegate id, incomplete lifecycle contract, protocol version mismatch or
170
+ * duplicate invocation id.
125
171
  */
126
172
  register(input) {
127
- const { id, kind, description, audience, effect, intentIds, invocation, contribution, parseInput: inputParser, parseOutput: outputParser, available: availability, execute, debugSteps, } = input;
173
+ const { id, kind, description, audience, effect, goalIds, delegate, lifecycle, parseInput: inputParser, parseOutput: outputParser, active: availability, execute, debugSteps, } = input;
128
174
  const parseInput = inputParser?.bind(input), parseOutput = outputParser?.bind(input);
129
- const available = availability?.bind(input);
130
- const frozenIntents = Array.isArray(intentIds) ? Object.freeze([...intentIds]) : intentIds;
175
+ const available = typeof availability === 'function' ? availability.bind(input) : availability;
176
+ const frozenGoalIds = Array.isArray(goalIds) ? Object.freeze([...goalIds]) : goalIds;
177
+ // Validate the declaration before projecting it into a model tool: a sealed or invalid
178
+ // registration must report its configured error instead of a schema-projection failure.
179
+ this.assertRegistration(input, { parseInput, parseOutput, execute });
180
+ const modelTool = new model_js_1.ModelToolRegistry([
181
+ {
182
+ id,
183
+ description,
184
+ inputSchema: input.inputSchema ?? { jsonSchema: { type: 'object' }, parse: parseInput },
185
+ resolve: input.resolve?.bind(input) ?? ((value) => ({ toolId: id, input: value })),
186
+ active: typeof input.active === 'function' ? input.active.bind(input) : input.active,
187
+ asIntent: typeof input.asIntent === 'function' ? input.asIntent.bind(input) : input.asIntent,
188
+ },
189
+ ]).require(id);
131
190
  const tool = {
132
191
  ...(0, registration_js_1.registrationData)(input, [
133
192
  'id',
@@ -135,148 +194,148 @@ class ToolLifecycleRegistry {
135
194
  'description',
136
195
  'audience',
137
196
  'effect',
138
- 'intentIds',
139
- 'invocation',
140
- 'contribution',
197
+ 'goalIds',
198
+ 'delegate',
199
+ 'lifecycle',
141
200
  'parseInput',
142
201
  'parseOutput',
143
202
  'execute',
144
203
  'debugSteps',
145
- 'available',
204
+ 'active',
146
205
  ]),
206
+ ...modelTool,
147
207
  id,
148
208
  kind,
149
209
  description,
150
210
  audience,
151
211
  effect,
152
- invocation,
153
- contribution,
154
- intentIds: frozenIntents,
212
+ delegate,
213
+ lifecycle,
214
+ goalIds: frozenGoalIds,
155
215
  parseInput,
156
216
  parseOutput,
157
217
  debugSteps: debugSteps?.bind(input),
158
218
  execute: execute?.bind(input),
159
- available: available
160
- ? (context) => (0, synchronous_js_1.booleanResult)(available(context), 'Tool availability must be synchronous', 'Tool availability must return boolean')
161
- : undefined,
219
+ active: available,
162
220
  };
163
- const bindMethods = (value, keys, data = {}) => Object.freeze({
164
- ...(0, registration_js_1.registrationData)(value, [...keys, ...Reflect.ownKeys(data)]),
165
- ...data,
166
- ...Object.fromEntries(keys.map((key) => {
167
- const handler = value[key];
168
- return [
169
- key,
170
- typeof handler === 'function'
171
- ? (...args) => (['authorize', 'inspect', 'finalize'].includes(String(key))
172
- ? synchronous_js_1.assertion
173
- : synchronous_js_1.synchronous)(handler.apply(value, args), `Tool lifecycle ${String(key)} must finish synchronously with the declared result`)
174
- : handler,
175
- ];
176
- })),
177
- });
178
221
  const registered = Object.freeze({
179
222
  ...tool,
180
- ...(invocation
181
- ? {
182
- invocation: bindMethods(invocation, ['call', 'inspect', 'authorize', 'resume', 'complete'], { step: invocation.step, protocol: invocation.protocol }),
183
- }
184
- : {}),
185
- ...(contribution
186
- ? {
187
- contribution: bindMethods(contribution, [
188
- 'composition',
189
- 'inspect',
190
- 'recoveryContext',
191
- 'projectContext',
192
- 'visibleText',
193
- 'reviewDraft',
194
- 'finalize',
195
- 'missingInteractionAllowed',
196
- 'rules',
197
- 'modelFields',
198
- 'supportsIntent',
199
- ]),
200
- }
201
- : {}),
223
+ ...(lifecycle ? { lifecycle: bindLifecycle(lifecycle) } : {}),
202
224
  });
203
- return this.install(input, registered);
225
+ return this.install(input, registered, true);
204
226
  }
205
- install(input, tool) {
206
- (0, rules_js_1.breakWhile)(this.sealed, 'Tool registry is sealed');
227
+ /** Validate one declaration in the fixed order documented by `register`. */
228
+ assertRegistration(input, handlers) {
207
229
  const policy = this.options.registration;
208
230
  const identityPredicate = policy.isValidId;
209
231
  const isValidId = () => (0, synchronous_js_1.booleanResult)(identityPredicate(input), 'Tool identity predicates must be synchronous', 'Tool identity predicates must return boolean');
210
232
  const invalid = policy.invalidTool ?? 'Invalid registered tool';
211
233
  const invalidInvocation = policy.invalidInvocation ?? 'Invalid or duplicate tool invocation';
234
+ const { lifecycle } = input;
235
+ // A lifecycle block may declare hooks only. Declaring any invocation part makes all three
236
+ // required, so a partial declaration fails instead of silently never running.
237
+ const ownsInvocation = !!lifecycle &&
238
+ (lifecycle.id !== undefined ||
239
+ lifecycle.protocol !== undefined ||
240
+ lifecycle.call !== undefined);
241
+ const { parseInput, parseOutput, execute } = handlers;
212
242
  (0, rules_js_1.breakWhile)([
243
+ { occur: () => this.sealed, error: 'Tool registry is sealed', tags: ['sealed'] },
213
244
  {
214
- occur: [() => !policy.kinds.includes(tool.kind), () => !isValidId()],
245
+ occur: [() => !policy.kinds.includes(input.kind), () => !isValidId()],
215
246
  error: invalid,
216
247
  tags: ['identity'],
217
248
  },
218
- { occur: () => !tool.description.trim(), error: invalid, tags: ['description'] },
249
+ {
250
+ occur: () => typeof input.description !== 'string' || !input.description.trim(),
251
+ error: invalid,
252
+ tags: ['description'],
253
+ },
254
+ {
255
+ occur: () => input.active !== undefined &&
256
+ typeof input.active !== 'boolean' &&
257
+ typeof input.active !== 'function',
258
+ error: invalid,
259
+ tags: ['active'],
260
+ },
219
261
  {
220
262
  occur: [
221
- () => !policy.audiences.includes(tool.audience),
222
- () => !policy.effects.includes(tool.effect),
263
+ () => !policy.audiences.includes(input.audience),
264
+ () => !policy.effects.includes(input.effect),
223
265
  ],
224
266
  error: invalid,
225
267
  tags: ['execution-contract'],
226
268
  },
227
269
  {
228
270
  occur: [
229
- () => tool.intentIds !== 'all' && !Array.isArray(tool.intentIds),
230
- () => tool.intentIds !== 'all' && !tool.intentIds.length,
231
- () => tool.intentIds !== 'all' &&
232
- tool.intentIds.some((goal) => !(0, synchronous_js_1.booleanResult)(policy.intentExists(goal), 'Intent lookup must return boolean synchronously')),
233
- () => tool.intentIds !== 'all' && new Set(tool.intentIds).size !== tool.intentIds.length,
271
+ () => input.goalIds !== 'all' && !Array.isArray(input.goalIds),
272
+ () => input.goalIds !== 'all' && !input.goalIds.length,
273
+ () => input.goalIds !== 'all' &&
274
+ input.goalIds.some((goal) => !(0, synchronous_js_1.booleanResult)(policy.goalExists(goal), 'Goal lookup must return boolean synchronously')),
275
+ () => input.goalIds !== 'all' && new Set(input.goalIds).size !== input.goalIds.length,
234
276
  ],
235
277
  error: invalid,
236
- tags: ['intents'],
278
+ tags: ['goals'],
237
279
  },
238
280
  {
239
- occur: () => ![tool.parseInput, tool.parseOutput, tool.execute].every((handler) => typeof handler === 'function'),
281
+ occur: () => ![parseInput, parseOutput, execute].every((handler) => typeof handler === 'function'),
240
282
  error: invalid,
241
283
  tags: ['handlers'],
242
284
  },
243
285
  {
244
- occur: () => this.tools.has(tool.id),
245
- error: () => new Error(`Duplicate registered tool: ${tool.id}`),
286
+ occur: () => this.tools.has(input.id),
287
+ error: () => new Error(`Duplicate registered tool: ${input.id}`),
246
288
  tags: ['duplicate-name'],
247
289
  },
248
290
  {
249
- occur: () => tool.invocation && !tool.invocation.step,
291
+ occur: () => !!input.delegate && this.delegateIndex.has(input.delegate),
292
+ error: () => new Error(`Duplicate registered delegate: ${input.delegate}`),
293
+ tags: ['delegate', 'duplicate-id'],
294
+ },
295
+ {
296
+ occur: () => ownsInvocation && !lifecycle.id,
250
297
  error: invalidInvocation,
251
- tags: ['invocation', 'step'],
298
+ tags: ['lifecycle', 'identity'],
252
299
  },
253
300
  {
254
- occur: [
255
- () => tool.invocation && typeof tool.invocation.call !== 'function',
256
- () => tool.invocation && typeof tool.invocation.protocol?.keys !== 'function',
257
- ],
301
+ occur: () => ownsInvocation && typeof lifecycle.call !== 'function',
302
+ error: invalidInvocation,
303
+ tags: ['lifecycle', 'contract'],
304
+ },
305
+ {
306
+ occur: () => ownsInvocation && typeof lifecycle.protocol?.keys !== 'function',
258
307
  error: invalidInvocation,
259
- tags: ['invocation', 'contract'],
308
+ tags: ['lifecycle', 'contract'],
260
309
  },
261
310
  {
262
- occur: () => tool.invocation &&
311
+ occur: () => ownsInvocation &&
263
312
  policy.protocolVersion !== undefined &&
264
- tool.invocation.protocol.version !== undefined &&
265
- tool.invocation.protocol.version !== policy.protocolVersion,
313
+ lifecycle.protocol?.version !== undefined &&
314
+ lifecycle.protocol.version !== policy.protocolVersion,
266
315
  error: invalidInvocation,
267
- tags: ['invocation', 'version'],
316
+ tags: ['lifecycle', 'version'],
268
317
  },
269
318
  {
270
- occur: () => tool.invocation && this.invocations.has(tool.invocation.step),
319
+ occur: () => !!lifecycle?.id && this.invocations.has(lifecycle.id),
271
320
  error: invalidInvocation,
272
- tags: ['invocation', 'duplicate-step'],
321
+ tags: ['lifecycle', 'duplicate-id'],
273
322
  },
274
323
  ], policy.tags);
324
+ }
325
+ install(input, tool, validated = false) {
326
+ if (!validated)
327
+ this.assertRegistration(input, {
328
+ parseInput: tool.parseInput,
329
+ parseOutput: tool.parseOutput,
330
+ execute: tool.execute,
331
+ });
275
332
  this.tools.register(tool);
276
333
  this.registered.set(tool.id, tool);
277
334
  this.sources.set(tool.id, input);
278
- if (tool.invocation)
279
- this.invocations.set(tool.invocation.step, tool);
335
+ if (tool.lifecycle?.id)
336
+ this.invocations.set(tool.lifecycle.id, tool);
337
+ if (tool.delegate)
338
+ this.delegateIndex.set(tool.delegate, tool);
280
339
  return this;
281
340
  }
282
341
  /** Register every tool in iteration order. */
@@ -328,6 +387,10 @@ class ToolLifecycleRegistry {
328
387
  values() {
329
388
  return this.registered.values();
330
389
  }
390
+ /** Whether a delegate id is claimed by a registered tool. */
391
+ hasDelegate(id) {
392
+ return this.delegateIndex.has(id);
393
+ }
331
394
  /**
332
395
  * Install the single normalization handler.
333
396
  *
@@ -363,134 +426,142 @@ class ToolLifecycleRegistry {
363
426
  return (0, synchronous_js_1.synchronous)(this.finalizer.call(receiver, receiver, decision, context), 'Tool finalization must be synchronous');
364
427
  }
365
428
  /**
366
- * Execute the invocation registered for a step type.
429
+ * Run the invocation that answers one decision next.
430
+ *
431
+ * Delegation is the App's choice; this method only executes the tool that claimed the id, so a
432
+ * purely programmatic node, a model choice and a mixed pipeline all dispatch the same way.
367
433
  *
368
434
  * Diagnostics are swapped to the invocation's own sink for the duration of the call and
369
435
  * restored afterwards, so nested tool calls report under the innermost scope.
370
436
  *
371
- * @param step - Step carrying the registered `type`.
437
+ * @param next - Decision next carrying the claimed delegate id in `type`.
372
438
  * @param context - Execution context forwarded to Core's invocation executor.
373
- * @throws A configured error when the step is unknown or the tool is unavailable.
439
+ * @throws A configured error when the delegate is unknown or the tool is unavailable.
374
440
  */
375
- call(step, context) {
376
- const tool = this.invocations.get(step.type);
441
+ call(next, context) {
442
+ const tool = this.delegateIndex.get(next.type) ?? this.invocations.get(next.type);
377
443
  (0, rules_js_1.breakWhile)([
378
444
  {
379
- occur: () => !tool?.invocation,
380
- error: () => this.options.unavailableStep?.(step.type) ??
381
- new Error(`Tool step is not registered: ${step.type}`),
445
+ occur: () => !tool?.lifecycle,
446
+ error: () => this.options.unavailableInvocation?.(next.type) ??
447
+ new Error(`Tool invocation is not registered: ${next.type}`),
382
448
  },
383
449
  {
384
- occur: () => tool.available && !tool.available(context),
450
+ occur: () => !(0, tool_conditions_js_1.toolActive)(tool.active, context),
385
451
  error: () => this.options.forbidden?.() ?? new Error('Tool is not available'),
386
452
  },
387
453
  ]);
388
454
  (0, invocation_js_2.executeInvocation)({
389
455
  id: tool.id,
390
- call: (value, input, execution) => {
456
+ call: (input, executionContext, execution) => {
391
457
  const previous = this.activeDiagnostics;
392
458
  this.activeDiagnostics = execution.diagnostics;
393
459
  try {
394
- tool.invocation.call(value, input, execution);
460
+ tool.lifecycle.call(input, executionContext, execution);
395
461
  }
396
462
  finally {
397
463
  this.activeDiagnostics = previous;
398
464
  }
399
465
  },
400
- }, step, context, this.invocationPorts);
466
+ }, next, context, this.invocationPorts);
401
467
  }
402
- /** Output protocols plus one protocol per registered invocation step. */
468
+ /** Output protocols plus one protocol per registered invocation. */
403
469
  protocols() {
404
- return new protocol_registry_js_1.ProtocolRegistry([
405
- ...this.options.outputs(),
406
- ...[...this.invocations].map(([step, tool]) => [step, tool.invocation.protocol]),
407
- ]);
470
+ return new protocol_registry_js_1.ProtocolRegistry([...this.options.outputs(), ...this.invocationProtocols()]);
408
471
  }
409
472
  /**
410
- * Protocols exposed to the model: output protocols plus steps whose audience is `model` and
473
+ * Protocols exposed to the model: output protocols plus invocations whose audience is `model` and
411
474
  * which are currently available in `context`.
412
475
  */
413
476
  modelProtocols(context) {
414
477
  return new protocol_registry_js_1.ProtocolRegistry([
415
478
  ...(this.options.modelOutputs?.(context) ?? this.options.outputs()),
416
- ...[...this.invocations]
417
- .filter(([, tool]) => tool.audience === 'model' && (!tool.available || tool.available(context)))
418
- .map(([step, tool]) => [step, tool.invocation.protocol]),
479
+ ...this.invocationProtocols((tool) => tool.audience === 'model' && (0, tool_conditions_js_1.toolActive)(tool.active, context)),
419
480
  ]);
420
481
  }
421
- /** Whether the protocol registered for this step is declared terminal. */
422
- isTerminal(step) {
423
- return this.protocols().get(step.type)?.terminal === true;
482
+ /** Whether the protocol registered for this invocation is declared terminal. */
483
+ isTerminal(next) {
484
+ return this.protocols().get(next.type)?.terminal === true;
485
+ }
486
+ /** `[invocationId, protocol]` pairs, optionally filtered by the owning tool. */
487
+ invocationProtocols(keep) {
488
+ return [...this.invocations]
489
+ .filter(([, tool]) => !keep || keep(tool))
490
+ .map(([id, tool]) => [id, tool.lifecycle.protocol]);
424
491
  }
425
- /** Run contribution inspection for every tool, then the selected invocation's own check. */
492
+ /**
493
+ * Run `inspect` for every registered tool in registration order.
494
+ *
495
+ * `inspect` belongs to the all-tools group, so the selected tool participates exactly once
496
+ * through the same loop rather than through a second selected-only pass.
497
+ */
426
498
  assertDecision(decision, context) {
427
499
  for (const tool of this.registered.values())
428
- tool.contribution?.inspect?.(decision, context);
429
- this.selected(decision)?.invocation?.inspect?.(decision, context);
500
+ tool.lifecycle?.inspect?.(decision, context);
430
501
  }
431
502
  /** Run the selected invocation's authorization hook, if any. */
432
503
  authorize(decision, context) {
433
- this.selected(decision)?.invocation?.authorize?.(decision, context);
504
+ this.selected(decision)?.lifecycle?.authorize?.(decision, context);
434
505
  }
435
506
  /** Whether the selected invocation should resume rather than start fresh; defaults to false. */
436
507
  resume(decision, context) {
437
- const invocation = this.selected(decision)?.invocation;
438
- return invocation?.resume
439
- ? (0, synchronous_js_1.booleanResult)(invocation.resume(decision, context), 'Tool resume must return boolean synchronously')
508
+ const lifecycle = this.selected(decision)?.lifecycle;
509
+ return lifecycle?.resume
510
+ ? (0, synchronous_js_1.booleanResult)(lifecycle.resume(decision, context), 'Tool resume must return boolean synchronously')
440
511
  : false;
441
512
  }
442
513
  /** Let the selected invocation rewrite the decision on completion, if it declares a hook. */
443
514
  complete(decision, context) {
444
- return this.selected(decision)?.invocation?.complete?.(decision, context);
515
+ return this.selected(decision)?.lifecycle?.complete?.(decision, context);
445
516
  }
446
- /** Fold every contribution's context projection over the value, in registration order. */
517
+ /** Fold every tool's context projection over the value, in registration order. */
447
518
  projectContext(value) {
448
- return [...this.registered.values()].reduce((current, tool) => tool.contribution?.projectContext?.(current) ?? current, value);
519
+ return [...this.registered.values()].reduce((current, tool) => tool.lifecycle?.projectContext?.(current) ?? current, value);
449
520
  }
450
- /** Fold every contribution's visible-text transform over the text. */
521
+ /** Fold every tool's visible-text transform over the text. */
451
522
  visibleText(text, context) {
452
- return [...this.registered.values()].reduce((value, tool) => tool.contribution?.visibleText?.(value, context) ?? value, text);
523
+ return [...this.registered.values()].reduce((value, tool) => tool.lifecycle?.visibleText?.(value, context) ?? value, text);
453
524
  }
454
- /** Fold every contribution's review transform over the draft. */
525
+ /** Fold every tool's review transform over the draft. */
455
526
  reviewDraft(draft, context) {
456
- return [...this.registered.values()].reduce((value, tool) => tool.contribution?.reviewDraft?.(value, context) ?? value, draft);
527
+ return [...this.registered.values()].reduce((value, tool) => tool.lifecycle?.reviewDraft?.(value, context) ?? value, draft);
457
528
  }
458
529
  /** Collected recovery context values; `undefined` results are omitted. */
459
530
  recoveryContext(context) {
460
531
  return [...this.registered.values()].flatMap((tool) => {
461
- const value = tool.contribution?.recoveryContext?.(context);
532
+ const value = tool.lifecycle?.recoveryContext?.(context);
462
533
  return value === undefined ? [] : [value];
463
534
  });
464
535
  }
465
536
  /**
466
537
  * The single output template that owns this delivery.
467
538
  *
468
- * @throws An error when more than one contribution claims composition, because delivery
469
- * would otherwise depend on registration order.
539
+ * @throws An error when more than one tool claims composition, because delivery would
540
+ * otherwise depend on registration order.
470
541
  */
471
542
  composition(context) {
472
543
  const values = [...this.registered.values()].flatMap((tool) => {
473
- const value = tool.contribution?.composition?.(context);
544
+ const value = tool.lifecycle?.composition?.(context);
474
545
  return value ? [value] : [];
475
546
  });
476
547
  (0, rules_js_1.breakWhile)(values.length > 1, 'More than one output template owns this delivery');
477
548
  return values[0];
478
549
  }
479
- /** Run every contribution's finalize hook for the given phase. */
550
+ /** Run every tool's finalize hook for the given phase. */
480
551
  finalize(decision, context, phase) {
481
552
  for (const tool of this.registered.values())
482
- tool.contribution?.finalize?.(decision, context, phase);
553
+ tool.lifecycle?.finalize?.(decision, context, phase);
483
554
  }
484
- /** Whether any contribution permits the interaction to be missing. */
555
+ /** Whether any tool permits the interaction to be missing. */
485
556
  missingInteractionAllowed(decision, context) {
486
- return [...this.registered.values()].some((tool) => tool.contribution?.missingInteractionAllowed
487
- ? (0, synchronous_js_1.booleanResult)(tool.contribution.missingInteractionAllowed(decision, context), 'Missing interaction predicates must return boolean synchronously')
557
+ return [...this.registered.values()].some((tool) => tool.lifecycle?.missingInteractionAllowed
558
+ ? (0, synchronous_js_1.booleanResult)(tool.lifecycle.missingInteractionAllowed(decision, context), 'Missing interaction predicates must return boolean synchronously')
488
559
  : false);
489
560
  }
490
561
  /** Task rules ordered by their declared `order`, joined into one prompt block. */
491
562
  taskRules(context) {
492
563
  return [...this.registered.values()]
493
- .flatMap((tool) => tool.contribution?.rules?.(context) ?? [])
564
+ .flatMap((tool) => tool.lifecycle?.rules?.(context) ?? [])
494
565
  .sort((left, right) => left.order - right.order)
495
566
  .map((rule) => rule.text)
496
567
  .filter(Boolean)
@@ -499,7 +570,7 @@ class ToolLifecycleRegistry {
499
570
  /** Model-context fragments for one placement, ordered by each field's `order`. */
500
571
  contextFields(context, placement) {
501
572
  return [...this.registered.values()]
502
- .flatMap((tool) => (tool.contribution?.modelFields?.(context, placement) ?? [])
573
+ .flatMap((tool) => (tool.lifecycle?.modelFields?.(context, placement) ?? [])
503
574
  .filter((field) => field.placement === placement)
504
575
  .map((field) => ({ tool, field })))
505
576
  .sort((left, right) => left.field.order - right.field.order)
@@ -507,11 +578,25 @@ class ToolLifecycleRegistry {
507
578
  }
508
579
  /** Merged model-context values for one placement; later fragments win per key. */
509
580
  modelFields(context, placement) {
510
- return Object.assign({}, ...this.contextFields(context, placement).map((fragment) => fragment.value));
511
- }
512
- /** A contribution may veto an intent by returning `false`; silence means support. */
513
- supportsIntent(intentId, context) {
514
- return [...this.registered.values()].every((tool) => tool.contribution?.supportsIntent?.(intentId, context) !== false);
581
+ const fields = {};
582
+ for (const { value } of this.contextFields(context, placement)) {
583
+ if (!value || typeof value !== 'object' || Array.isArray(value))
584
+ continue;
585
+ for (const [key, entry] of Object.entries(value))
586
+ // Own data properties keep keys such as `__proto__` from rewriting the merged result's
587
+ // prototype, matching how context assembly writes structured fields.
588
+ Object.defineProperty(fields, key, {
589
+ value: entry,
590
+ enumerable: true,
591
+ configurable: true,
592
+ writable: true,
593
+ });
594
+ }
595
+ return fields;
596
+ }
597
+ /** A tool may veto a goal by returning `false`; silence means support. */
598
+ supportsGoal(intentId, context) {
599
+ return [...this.registered.values()].every((tool) => tool.lifecycle?.supportsGoal?.(intentId, context) !== false);
515
600
  }
516
601
  /**
517
602
  * Describe registered tools, optionally limited by audience and availability.
@@ -521,19 +606,22 @@ class ToolLifecycleRegistry {
521
606
  *
522
607
  * @param audience - Audience to keep; omit to include every audience.
523
608
  * @param context - Context for availability checks.
524
- * @returns Plain metadata; `intentIds: 'all'` is expanded to the current intent ids.
609
+ * @returns Plain metadata; `goalIds: 'all'` is expanded to the current goal ids.
525
610
  */
526
611
  describe(audience, context) {
527
612
  return [...this.registered.values()]
528
613
  .filter((tool) => (!audience || tool.audience === audience) &&
529
- (!tool.available || (!!context && tool.available(context))))
530
- .map(({ id, kind, description, audience, effect, intentIds }) => ({
614
+ (tool.active === undefined ||
615
+ (typeof tool.active === 'boolean'
616
+ ? (0, tool_conditions_js_1.toolActive)(tool.active, context)
617
+ : !!context && (0, tool_conditions_js_1.toolActive)(tool.active, context))))
618
+ .map(({ id, kind, description, audience, effect, goalIds }) => ({
531
619
  id,
532
620
  kind,
533
621
  description,
534
622
  audience,
535
623
  effect,
536
- intentIds: intentIds === 'all' ? [...this.options.intentIds()] : [...intentIds],
624
+ goalIds: goalIds === 'all' ? [...this.options.goalIds()] : [...goalIds],
537
625
  }));
538
626
  }
539
627
  /**
@@ -556,9 +644,9 @@ class ToolLifecycleRegistry {
556
644
  this.tools.seal();
557
645
  return this;
558
646
  }
559
- /** Tool that owns the invocation step named by the decision, if any. */
647
+ /** Tool that owns the invocation id named by the decision, if any. */
560
648
  selected(decision) {
561
- return this.invocations.get(this.options.step(decision));
649
+ return this.invocations.get(this.options.invocationId(decision));
562
650
  }
563
651
  }
564
652
  exports.ToolLifecycleRegistry = ToolLifecycleRegistry;