@aria-cli/tools 1.0.12 → 1.0.14

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 (233) hide show
  1. package/dist/index.js +378 -70
  2. package/dist/network-runtime/index.js +8 -12
  3. package/dist-cjs/index.js +400 -435
  4. package/dist-cjs/network-runtime/index.js +8 -172
  5. package/package.json +8 -6
  6. package/dist/.tsbuildinfo +0 -1
  7. package/dist/ask-user-interaction.js +0 -22
  8. package/dist/cache/web-cache.js +0 -66
  9. package/dist/definitions/arion.js +0 -104
  10. package/dist/definitions/browser/browser.js +0 -418
  11. package/dist/definitions/browser/index.js +0 -4
  12. package/dist/definitions/browser/pw-downloads.js +0 -114
  13. package/dist/definitions/browser/pw-interactions.js +0 -199
  14. package/dist/definitions/browser/pw-responses.js +0 -76
  15. package/dist/definitions/browser/pw-session.js +0 -310
  16. package/dist/definitions/browser/pw-shared.js +0 -66
  17. package/dist/definitions/browser/pw-snapshot.js +0 -301
  18. package/dist/definitions/browser/pw-state.js +0 -62
  19. package/dist/definitions/browser/types.js +0 -4
  20. package/dist/definitions/code-intelligence.js +0 -470
  21. package/dist/definitions/core.js +0 -109
  22. package/dist/definitions/delegation.js +0 -512
  23. package/dist/definitions/deploy.js +0 -65
  24. package/dist/definitions/filesystem.js +0 -196
  25. package/dist/definitions/frg.js +0 -63
  26. package/dist/definitions/index.js +0 -20
  27. package/dist/definitions/memory.js +0 -123
  28. package/dist/definitions/messaging.js +0 -625
  29. package/dist/definitions/meta.js +0 -349
  30. package/dist/definitions/network.js +0 -159
  31. package/dist/definitions/outlook.js +0 -277
  32. package/dist/definitions/patch/apply-patch.js +0 -184
  33. package/dist/definitions/patch/fuzzy-match.js +0 -166
  34. package/dist/definitions/patch/index.js +0 -1
  35. package/dist/definitions/patch/patch-parser.js +0 -207
  36. package/dist/definitions/patch/sandbox-paths.js +0 -105
  37. package/dist/definitions/process/index.js +0 -4
  38. package/dist/definitions/process/process-registry.js +0 -213
  39. package/dist/definitions/process/process.js +0 -386
  40. package/dist/definitions/process/pty-keys.js +0 -254
  41. package/dist/definitions/process/session-slug.js +0 -142
  42. package/dist/definitions/quip.js +0 -195
  43. package/dist/definitions/search.js +0 -60
  44. package/dist/definitions/session-history.js +0 -69
  45. package/dist/definitions/shell.js +0 -181
  46. package/dist/definitions/slack.js +0 -180
  47. package/dist/definitions/web.js +0 -109
  48. package/dist/executors/apply-patch.js +0 -901
  49. package/dist/executors/arion.js +0 -119
  50. package/dist/executors/code-intelligence.js +0 -882
  51. package/dist/executors/deploy.js +0 -848
  52. package/dist/executors/filesystem.js +0 -1122
  53. package/dist/executors/frg-freshness.js +0 -576
  54. package/dist/executors/frg.js +0 -298
  55. package/dist/executors/index.js +0 -46
  56. package/dist/executors/learning-meta.js +0 -1146
  57. package/dist/executors/lsp-client.js +0 -296
  58. package/dist/executors/memory.js +0 -750
  59. package/dist/executors/meta.js +0 -220
  60. package/dist/executors/process-registry.js +0 -465
  61. package/dist/executors/pty-session-store.js +0 -30
  62. package/dist/executors/pty.js +0 -271
  63. package/dist/executors/restart.js +0 -119
  64. package/dist/executors/search-freshness.js +0 -195
  65. package/dist/executors/search-types.js +0 -52
  66. package/dist/executors/search.js +0 -66
  67. package/dist/executors/self-diagnose.js +0 -398
  68. package/dist/executors/session-history.js +0 -283
  69. package/dist/executors/shell-safety.js +0 -473
  70. package/dist/executors/shell.js +0 -954
  71. package/dist/executors/utils.js +0 -33
  72. package/dist/executors/web.js +0 -542
  73. package/dist/extraction/content-extraction.js +0 -235
  74. package/dist/extraction/index.js +0 -4
  75. package/dist/headless-control-contract.js +0 -967
  76. package/dist/local-control-http-auth.js +0 -2
  77. package/dist/mcp/client.js +0 -181
  78. package/dist/mcp/connection.js +0 -480
  79. package/dist/mcp/index.js +0 -10
  80. package/dist/mcp/jsonrpc.js +0 -144
  81. package/dist/mcp/types.js +0 -7
  82. package/dist/network-control-adapter.js +0 -72
  83. package/dist/network-runtime/address-types.js +0 -165
  84. package/dist/network-runtime/db-owner-fencing.js +0 -69
  85. package/dist/network-runtime/delivery-receipts.js +0 -267
  86. package/dist/network-runtime/direct-endpoint-authority.js +0 -25
  87. package/dist/network-runtime/local-control-contract.js +0 -627
  88. package/dist/network-runtime/node-store-contract.js +0 -34
  89. package/dist/network-runtime/pair-route-contract.js +0 -77
  90. package/dist/network-runtime/peer-capabilities.js +0 -28
  91. package/dist/network-runtime/peer-principal-ref.js +0 -12
  92. package/dist/network-runtime/peer-state-machine.js +0 -121
  93. package/dist/network-runtime/protocol-schemas.js +0 -205
  94. package/dist/network-runtime/runtime-bootstrap-contract.js +0 -60
  95. package/dist/outlook/desktop-session.js +0 -279
  96. package/dist/policy.js +0 -149
  97. package/dist/providers/brave.js +0 -62
  98. package/dist/providers/duckduckgo.js +0 -176
  99. package/dist/providers/exa.js +0 -63
  100. package/dist/providers/firecrawl.js +0 -55
  101. package/dist/providers/index.js +0 -7
  102. package/dist/providers/jina.js +0 -49
  103. package/dist/providers/router.js +0 -96
  104. package/dist/providers/search-provider.js +0 -32
  105. package/dist/providers/tavily.js +0 -54
  106. package/dist/quip/desktop-session.js +0 -317
  107. package/dist/registry/index.js +0 -1
  108. package/dist/registry/registry.js +0 -756
  109. package/dist/runtime-socket-local-control-client.js +0 -330
  110. package/dist/security/dns-normalization.js +0 -19
  111. package/dist/security/dns-pinning.js +0 -123
  112. package/dist/security/external-content.js +0 -91
  113. package/dist/security/ssrf.js +0 -181
  114. package/dist/slack/desktop-session.js +0 -324
  115. package/dist/tool-factory.js +0 -47
  116. package/dist/types.js +0 -7
  117. package/dist/utils/retry.js +0 -132
  118. package/dist/utils/safe-parse-json.js +0 -160
  119. package/dist/utils/url.js +0 -19
  120. package/dist-cjs/.tsbuildinfo +0 -1
  121. package/dist-cjs/ask-user-interaction.js +0 -27
  122. package/dist-cjs/cache/web-cache.js +0 -70
  123. package/dist-cjs/definitions/arion.js +0 -107
  124. package/dist-cjs/definitions/browser/browser.js +0 -421
  125. package/dist-cjs/definitions/browser/index.js +0 -8
  126. package/dist-cjs/definitions/browser/pw-downloads.js +0 -117
  127. package/dist-cjs/definitions/browser/pw-interactions.js +0 -213
  128. package/dist-cjs/definitions/browser/pw-responses.js +0 -84
  129. package/dist-cjs/definitions/browser/pw-session.js +0 -326
  130. package/dist-cjs/definitions/browser/pw-shared.js +0 -72
  131. package/dist-cjs/definitions/browser/pw-snapshot.js +0 -307
  132. package/dist-cjs/definitions/browser/pw-state.js +0 -70
  133. package/dist-cjs/definitions/browser/types.js +0 -5
  134. package/dist-cjs/definitions/code-intelligence.js +0 -473
  135. package/dist-cjs/definitions/core.js +0 -133
  136. package/dist-cjs/definitions/delegation.js +0 -515
  137. package/dist-cjs/definitions/deploy.js +0 -68
  138. package/dist-cjs/definitions/filesystem.js +0 -199
  139. package/dist-cjs/definitions/frg.js +0 -66
  140. package/dist-cjs/definitions/index.js +0 -43
  141. package/dist-cjs/definitions/memory.js +0 -126
  142. package/dist-cjs/definitions/messaging.js +0 -631
  143. package/dist-cjs/definitions/meta.js +0 -352
  144. package/dist-cjs/definitions/network.js +0 -162
  145. package/dist-cjs/definitions/outlook.js +0 -280
  146. package/dist-cjs/definitions/patch/apply-patch.js +0 -191
  147. package/dist-cjs/definitions/patch/fuzzy-match.js +0 -172
  148. package/dist-cjs/definitions/patch/index.js +0 -5
  149. package/dist-cjs/definitions/patch/patch-parser.js +0 -215
  150. package/dist-cjs/definitions/patch/sandbox-paths.js +0 -113
  151. package/dist-cjs/definitions/process/index.js +0 -8
  152. package/dist-cjs/definitions/process/process-registry.js +0 -231
  153. package/dist-cjs/definitions/process/process.js +0 -389
  154. package/dist-cjs/definitions/process/pty-keys.js +0 -259
  155. package/dist-cjs/definitions/process/session-slug.js +0 -145
  156. package/dist-cjs/definitions/quip.js +0 -198
  157. package/dist-cjs/definitions/search.js +0 -63
  158. package/dist-cjs/definitions/session-history.js +0 -72
  159. package/dist-cjs/definitions/shell.js +0 -184
  160. package/dist-cjs/definitions/slack.js +0 -183
  161. package/dist-cjs/definitions/web.js +0 -112
  162. package/dist-cjs/executors/apply-patch.js +0 -938
  163. package/dist-cjs/executors/arion.js +0 -125
  164. package/dist-cjs/executors/code-intelligence.js +0 -925
  165. package/dist-cjs/executors/deploy.js +0 -869
  166. package/dist-cjs/executors/filesystem.js +0 -1167
  167. package/dist-cjs/executors/frg-freshness.js +0 -627
  168. package/dist-cjs/executors/frg.js +0 -334
  169. package/dist-cjs/executors/index.js +0 -143
  170. package/dist-cjs/executors/learning-meta.js +0 -1165
  171. package/dist-cjs/executors/lsp-client.js +0 -310
  172. package/dist-cjs/executors/memory.js +0 -796
  173. package/dist-cjs/executors/meta.js +0 -226
  174. package/dist-cjs/executors/process-registry.js +0 -469
  175. package/dist-cjs/executors/pty-session-store.js +0 -34
  176. package/dist-cjs/executors/pty.js +0 -312
  177. package/dist-cjs/executors/restart.js +0 -155
  178. package/dist-cjs/executors/search-freshness.js +0 -234
  179. package/dist-cjs/executors/search-types.js +0 -56
  180. package/dist-cjs/executors/search.js +0 -102
  181. package/dist-cjs/executors/self-diagnose.js +0 -434
  182. package/dist-cjs/executors/session-history.js +0 -320
  183. package/dist-cjs/executors/shell-safety.js +0 -478
  184. package/dist-cjs/executors/shell.js +0 -1001
  185. package/dist-cjs/executors/utils.js +0 -73
  186. package/dist-cjs/executors/web.js +0 -547
  187. package/dist-cjs/extraction/content-extraction.js +0 -243
  188. package/dist-cjs/extraction/index.js +0 -8
  189. package/dist-cjs/headless-control-contract.js +0 -972
  190. package/dist-cjs/local-control-http-auth.js +0 -5
  191. package/dist-cjs/mcp/client.js +0 -185
  192. package/dist-cjs/mcp/connection.js +0 -484
  193. package/dist-cjs/mcp/index.js +0 -30
  194. package/dist-cjs/mcp/jsonrpc.js +0 -148
  195. package/dist-cjs/mcp/types.js +0 -8
  196. package/dist-cjs/network-control-adapter.js +0 -77
  197. package/dist-cjs/network-runtime/address-types.js +0 -168
  198. package/dist-cjs/network-runtime/db-owner-fencing.js +0 -76
  199. package/dist-cjs/network-runtime/delivery-receipts.js +0 -276
  200. package/dist-cjs/network-runtime/direct-endpoint-authority.js +0 -29
  201. package/dist-cjs/network-runtime/local-control-contract.js +0 -633
  202. package/dist-cjs/network-runtime/node-store-contract.js +0 -38
  203. package/dist-cjs/network-runtime/pair-route-contract.js +0 -80
  204. package/dist-cjs/network-runtime/peer-capabilities.js +0 -37
  205. package/dist-cjs/network-runtime/peer-principal-ref.js +0 -15
  206. package/dist-cjs/network-runtime/peer-state-machine.js +0 -129
  207. package/dist-cjs/network-runtime/protocol-schemas.js +0 -212
  208. package/dist-cjs/network-runtime/runtime-bootstrap-contract.js +0 -63
  209. package/dist-cjs/outlook/desktop-session.js +0 -318
  210. package/dist-cjs/policy.js +0 -155
  211. package/dist-cjs/providers/brave.js +0 -66
  212. package/dist-cjs/providers/duckduckgo.js +0 -180
  213. package/dist-cjs/providers/exa.js +0 -67
  214. package/dist-cjs/providers/firecrawl.js +0 -59
  215. package/dist-cjs/providers/index.js +0 -17
  216. package/dist-cjs/providers/jina.js +0 -53
  217. package/dist-cjs/providers/router.js +0 -100
  218. package/dist-cjs/providers/search-provider.js +0 -36
  219. package/dist-cjs/providers/tavily.js +0 -58
  220. package/dist-cjs/quip/desktop-session.js +0 -353
  221. package/dist-cjs/registry/index.js +0 -6
  222. package/dist-cjs/registry/registry.js +0 -761
  223. package/dist-cjs/runtime-socket-local-control-client.js +0 -367
  224. package/dist-cjs/security/dns-normalization.js +0 -22
  225. package/dist-cjs/security/dns-pinning.js +0 -160
  226. package/dist-cjs/security/external-content.js +0 -95
  227. package/dist-cjs/security/ssrf.js +0 -221
  228. package/dist-cjs/slack/desktop-session.js +0 -366
  229. package/dist-cjs/tool-factory.js +0 -50
  230. package/dist-cjs/types.js +0 -8
  231. package/dist-cjs/utils/retry.js +0 -169
  232. package/dist-cjs/utils/safe-parse-json.js +0 -164
  233. package/dist-cjs/utils/url.js +0 -23
@@ -1,756 +0,0 @@
1
- /**
2
- * Tool Registry - Manages tool registration and discovery
3
- */
4
- import { MCPClient } from "../mcp/client.js";
5
- import { executeBash } from "../executors/shell.js";
6
- /**
7
- * Validate tool input against a JSON Schema object.
8
- *
9
- * Lightweight validation that covers the schemas produced by our tool factory
10
- * (Zod -> JSON Schema): checks top-level type, required properties, property
11
- * types, enums, patterns, numeric/string/array bounds, additionalProperties,
12
- * and recursively validates nested object/array schemas.
13
- * Does not pull in a full JSON Schema validator like Ajv to keep the dependency
14
- * footprint minimal.
15
- *
16
- * @returns null if valid, or a human-readable error string
17
- */
18
- function isPlainObject(value) {
19
- return typeof value === "object" && value !== null && !Array.isArray(value);
20
- }
21
- function valueType(value) {
22
- if (value === null)
23
- return "null";
24
- if (Array.isArray(value))
25
- return "array";
26
- return typeof value;
27
- }
28
- function matchesJsonSchemaType(value, schemaType) {
29
- if (schemaType === "integer")
30
- return typeof value === "number" && Number.isInteger(value);
31
- if (schemaType === "number")
32
- return typeof value === "number";
33
- if (schemaType === "string")
34
- return typeof value === "string";
35
- if (schemaType === "boolean")
36
- return typeof value === "boolean";
37
- if (schemaType === "array")
38
- return Array.isArray(value);
39
- if (schemaType === "object")
40
- return isPlainObject(value);
41
- if (schemaType === "null")
42
- return value === null;
43
- return true;
44
- }
45
- function pathLabel(path) {
46
- return path ? `Property "${path}"` : "Value";
47
- }
48
- function validateValueAgainstSchema(value, schema, path) {
49
- const expectedType = schema.type;
50
- if (expectedType) {
51
- const types = Array.isArray(expectedType) ? expectedType : [expectedType];
52
- const typeMatches = types.some((typeName) => matchesJsonSchemaType(value, typeName));
53
- if (!typeMatches) {
54
- return `${pathLabel(path)} expected type ${types.join(" | ")}; got ${valueType(value)}`;
55
- }
56
- }
57
- if (Array.isArray(schema.enum) && schema.enum.length > 0) {
58
- const allowed = schema.enum;
59
- const hasValue = allowed.some((candidate) => Object.is(candidate, value));
60
- if (!hasValue) {
61
- return `${pathLabel(path)} must be one of: ${allowed.map((item) => String(item)).join(", ")}`;
62
- }
63
- }
64
- if ("const" in schema && !Object.is(schema.const, value)) {
65
- return `${pathLabel(path)} must equal ${String(schema.const)}`;
66
- }
67
- if (typeof value === "string") {
68
- if (typeof schema.minLength === "number" && value.length < schema.minLength) {
69
- return `${pathLabel(path)} must have length >= ${schema.minLength}`;
70
- }
71
- if (typeof schema.maxLength === "number" && value.length > schema.maxLength) {
72
- return `${pathLabel(path)} must have length <= ${schema.maxLength}`;
73
- }
74
- if (typeof schema.pattern === "string") {
75
- try {
76
- const pattern = new RegExp(schema.pattern);
77
- if (!pattern.test(value)) {
78
- return `${pathLabel(path)} does not match required pattern`;
79
- }
80
- }
81
- catch {
82
- // Invalid schema pattern: ignore rather than failing all validation.
83
- }
84
- }
85
- }
86
- if (typeof value === "number") {
87
- if (typeof schema.minimum === "number" && value < schema.minimum) {
88
- return `${pathLabel(path)} must be >= ${schema.minimum}`;
89
- }
90
- if (typeof schema.maximum === "number" && value > schema.maximum) {
91
- return `${pathLabel(path)} must be <= ${schema.maximum}`;
92
- }
93
- if (typeof schema.exclusiveMinimum === "number" && value <= schema.exclusiveMinimum) {
94
- return `${pathLabel(path)} must be > ${schema.exclusiveMinimum}`;
95
- }
96
- if (typeof schema.exclusiveMaximum === "number" && value >= schema.exclusiveMaximum) {
97
- return `${pathLabel(path)} must be < ${schema.exclusiveMaximum}`;
98
- }
99
- if (typeof schema.multipleOf === "number" && schema.multipleOf > 0) {
100
- const quotient = value / schema.multipleOf;
101
- if (!Number.isInteger(quotient)) {
102
- return `${pathLabel(path)} must be a multiple of ${schema.multipleOf}`;
103
- }
104
- }
105
- }
106
- if (Array.isArray(value)) {
107
- if (typeof schema.minItems === "number" && value.length < schema.minItems) {
108
- return `${pathLabel(path)} must contain at least ${schema.minItems} item(s)`;
109
- }
110
- if (typeof schema.maxItems === "number" && value.length > schema.maxItems) {
111
- return `${pathLabel(path)} must contain at most ${schema.maxItems} item(s)`;
112
- }
113
- if (isPlainObject(schema.items)) {
114
- for (let index = 0; index < value.length; index++) {
115
- const itemError = validateValueAgainstSchema(value[index], schema.items, path ? `${path}[${index}]` : `[${index}]`);
116
- if (itemError)
117
- return itemError;
118
- }
119
- }
120
- }
121
- if (isPlainObject(value)) {
122
- const required = Array.isArray(schema.required)
123
- ? schema.required.filter((item) => typeof item === "string")
124
- : [];
125
- if (required.length > 0) {
126
- const missing = required.filter((key) => !(key in value) || value[key] === undefined);
127
- if (missing.length > 0) {
128
- return `${path ? `${path}: ` : ""}Missing required properties: ${missing.join(", ")}`;
129
- }
130
- }
131
- const properties = isPlainObject(schema.properties)
132
- ? schema.properties
133
- : undefined;
134
- if (properties) {
135
- for (const [key, propSchema] of Object.entries(properties)) {
136
- if (!(key in value) || value[key] === undefined)
137
- continue;
138
- if (!isPlainObject(propSchema))
139
- continue;
140
- const childPath = path ? `${path}.${key}` : key;
141
- const nestedError = validateValueAgainstSchema(value[key], propSchema, childPath);
142
- if (nestedError)
143
- return nestedError;
144
- }
145
- }
146
- const additionalProperties = schema.additionalProperties;
147
- if (additionalProperties === false) {
148
- const allowed = new Set(properties ? Object.keys(properties) : []);
149
- const unknown = Object.keys(value).filter((key) => !allowed.has(key));
150
- if (unknown.length > 0) {
151
- if (path) {
152
- return `${path}: Unknown properties: ${unknown.join(", ")}`;
153
- }
154
- return `Unknown properties: ${unknown.join(", ")}`;
155
- }
156
- }
157
- else if (isPlainObject(additionalProperties)) {
158
- const allowed = new Set(properties ? Object.keys(properties) : []);
159
- for (const [extraKey, extraValue] of Object.entries(value)) {
160
- if (allowed.has(extraKey))
161
- continue;
162
- const childPath = path ? `${path}.${extraKey}` : extraKey;
163
- const apError = validateValueAgainstSchema(extraValue, additionalProperties, childPath);
164
- if (apError)
165
- return apError;
166
- }
167
- }
168
- }
169
- return null;
170
- }
171
- export function validateToolInput(input, schema, path = "") {
172
- // Only validate object-type schemas (all tool parameters are objects)
173
- if (schema.type !== "object")
174
- return null;
175
- const prefix = path ? `${path}: ` : "";
176
- // Input must be an object (or nullish, treated as empty object by many callers)
177
- if (input === null || input === undefined) {
178
- // If schema has required fields, this is invalid
179
- const required = schema.required;
180
- if (required && required.length > 0) {
181
- return `${prefix}Expected an object with required properties: ${required.join(", ")}; got ${input === null ? "null" : "undefined"}`;
182
- }
183
- return null;
184
- }
185
- if (typeof input !== "object" || Array.isArray(input)) {
186
- return `${prefix}Expected an object; got ${Array.isArray(input) ? "array" : typeof input}`;
187
- }
188
- return validateValueAgainstSchema(input, schema, path);
189
- }
190
- function isMcpToolReadOnly(tool) {
191
- return tool.annotations?.readOnlyHint === true;
192
- }
193
- function normalizeDiscoveryIssues(rawFailures) {
194
- if (!Array.isArray(rawFailures))
195
- return undefined;
196
- const issues = [];
197
- for (const failure of rawFailures) {
198
- if (typeof failure === "string") {
199
- const trimmed = failure.trim();
200
- if (trimmed)
201
- issues.push(trimmed);
202
- continue;
203
- }
204
- if (!failure || typeof failure !== "object")
205
- continue;
206
- const record = failure;
207
- const primary = typeof record.error === "string" && record.error.trim()
208
- ? record.error.trim()
209
- : typeof record.message === "string" && record.message.trim()
210
- ? record.message.trim()
211
- : undefined;
212
- if (primary)
213
- issues.push(primary);
214
- }
215
- if (issues.length === 0)
216
- return undefined;
217
- return [...new Set(issues)];
218
- }
219
- function isShellTemplate(template) {
220
- return template.trimStart().toLowerCase().startsWith("bash:");
221
- }
222
- function shellEscape(value) {
223
- return `'${value.replace(/'/g, `'\"'\"'`)}'`;
224
- }
225
- function renderTemplate(template, inputObj, options) {
226
- return template.replace(/\{\{(\w+)\}\}/g, (_match, key) => {
227
- const value = inputObj[key];
228
- if (value === undefined || value === null)
229
- return "";
230
- const stringValue = String(value);
231
- return options?.escapeForShell ? shellEscape(stringValue) : stringValue;
232
- });
233
- }
234
- export class ToolRegistry {
235
- tools = new Map();
236
- middleware = [];
237
- mcpClient;
238
- logger;
239
- constructor(options) {
240
- this.logger = options?.logger ?? console;
241
- }
242
- /**
243
- * Register a middleware to wrap tool execution.
244
- * Middlewares are applied in registration order (first registered = outermost).
245
- */
246
- use(mw) {
247
- this.middleware.push(mw);
248
- }
249
- /**
250
- * Register a tool
251
- * @throws Error if tool already registered and override not set
252
- */
253
- register(tool, options = {}) {
254
- if (this.tools.has(tool.name) && !options.override) {
255
- throw new Error(`Tool "${tool.name}" is already registered. Use { override: true } to replace.`);
256
- }
257
- this.tools.set(tool.name, tool);
258
- }
259
- /**
260
- * Get a tool by name
261
- */
262
- get(name) {
263
- return this.tools.get(name);
264
- }
265
- /**
266
- * Check if a tool is registered
267
- */
268
- has(name) {
269
- return this.tools.has(name);
270
- }
271
- /**
272
- * List all tools, optionally filtered by category
273
- */
274
- list(category) {
275
- const all = Array.from(this.tools.values());
276
- if (!category)
277
- return all;
278
- return all.filter((tool) => tool.category === category);
279
- }
280
- /**
281
- * Unregister a tool
282
- * @returns true if tool was removed, false if not found
283
- */
284
- unregister(name) {
285
- return this.tools.delete(name);
286
- }
287
- /**
288
- * Get count of registered tools
289
- */
290
- get size() {
291
- return this.tools.size;
292
- }
293
- /**
294
- * Clear all registered tools
295
- */
296
- clear() {
297
- this.tools.clear();
298
- }
299
- /**
300
- * Get all registered tools
301
- */
302
- getAll() {
303
- return Array.from(this.tools.values());
304
- }
305
- /**
306
- * Execute a tool by name with input validation.
307
- *
308
- * Validates `input` against the tool's JSON Schema `parameters` before
309
- * delegating to the tool's handler. Returns a structured error result
310
- * for unknown tools or validation failures instead of throwing.
311
- *
312
- * Tools without a `parameters` schema (or with a non-object schema)
313
- * skip validation and pass input directly to the handler.
314
- */
315
- async execute(name, input, context) {
316
- const tool = this.tools.get(name);
317
- if (!tool) {
318
- return {
319
- success: false,
320
- message: `Tool "${name}" is not registered.`,
321
- };
322
- }
323
- // Validate input against JSON Schema if the tool declares parameters
324
- if (tool.parameters && typeof tool.parameters === "object") {
325
- const schema = tool.parameters;
326
- const error = validateToolInput(input, schema);
327
- if (error) {
328
- return {
329
- success: false,
330
- message: `Invalid input for tool "${name}": ${error}`,
331
- };
332
- }
333
- }
334
- // If no middleware, execute directly
335
- if (this.middleware.length === 0) {
336
- return tool.execute(input, context);
337
- }
338
- // Run through middleware chain (first registered = outermost)
339
- const chain = this.middleware.reduceRight((next, mw) => () => mw(tool, input, context, next), () => tool.execute(input, context));
340
- return chain();
341
- }
342
- /**
343
- * Convert a Tool to ToolInfo for system prompt
344
- */
345
- toolToInfo(tool) {
346
- const parameters = [];
347
- if (tool.parameters && typeof tool.parameters === "object") {
348
- const schema = tool.parameters;
349
- if (schema.properties) {
350
- for (const [name, prop] of Object.entries(schema.properties)) {
351
- parameters.push({
352
- name,
353
- type: String(prop.type || "unknown"),
354
- required: schema.required?.includes(name) ?? false,
355
- description: prop.description,
356
- });
357
- }
358
- }
359
- }
360
- return {
361
- name: tool.name,
362
- description: tool.description,
363
- requiresConfirmation: tool.requiresConfirmation ?? tool.riskLevel === "dangerous",
364
- parameters,
365
- };
366
- }
367
- /**
368
- * Get tool info for all tools (for system prompt)
369
- */
370
- getToolInfos() {
371
- return this.getAll().map((tool) => this.toolToInfo(tool));
372
- }
373
- /**
374
- * Search for tools by name or description
375
- */
376
- search(query) {
377
- const queryLower = query.toLowerCase();
378
- return Array.from(this.tools.values()).filter((tool) => tool.name.toLowerCase().includes(queryLower) ||
379
- tool.description.toLowerCase().includes(queryLower));
380
- }
381
- /**
382
- * Discover and load tools from Memoria knowledge base
383
- *
384
- * @param memoria - Memoria instance to query
385
- * @returns Number of tools discovered and loaded
386
- */
387
- async discoverFromMemoria(memoria) {
388
- let count = 0;
389
- let offset = 0;
390
- let pagesLoaded = 0;
391
- const seenPageSignatures = new Set();
392
- const DISCOVERY_PAGE_SIZE = 200;
393
- const MAX_DISCOVERY_PAGES = 250;
394
- // Valid values for validation
395
- const validCategories = new Set([
396
- "filesystem",
397
- "code",
398
- "shell",
399
- "web",
400
- "data",
401
- "memory",
402
- "meta",
403
- "arion",
404
- ]);
405
- const validRiskLevels = new Set(["safe", "moderate", "dangerous"]);
406
- try {
407
- while (pagesLoaded < MAX_DISCOVERY_PAGES) {
408
- const tools = await memoria.recallTools({
409
- query: "",
410
- limit: DISCOVERY_PAGE_SIZE,
411
- offset,
412
- matchAll: true,
413
- updateAccessStats: false,
414
- });
415
- pagesLoaded += 1;
416
- if (tools.length === 0)
417
- break;
418
- const pageSignature = tools.map((tool) => String(tool.id ?? tool.name)).join("|");
419
- if (pageSignature && seenPageSignatures.has(pageSignature)) {
420
- this.logger.warn("Memoria discovery received a duplicate page; stopping to avoid pagination loop");
421
- break;
422
- }
423
- if (pageSignature) {
424
- seenPageSignatures.add(pageSignature);
425
- }
426
- for (const k of tools) {
427
- try {
428
- if (typeof k.name !== "string" || k.name.trim().length === 0) {
429
- this.logger.warn("Skipping Memoria tool with missing name");
430
- continue;
431
- }
432
- if (typeof k.description !== "string" || k.description.trim().length === 0) {
433
- this.logger.warn(`Skipping Memoria tool "${k.name}" with missing description`);
434
- continue;
435
- }
436
- const category = typeof k.category === "string" && validCategories.has(k.category)
437
- ? k.category
438
- : "meta";
439
- if (category === "meta" && k.category !== "meta") {
440
- this.logger.warn(`Memoria tool "${k.name}" has invalid/missing category; defaulting to "meta"`);
441
- }
442
- const parameters = k.parameters && typeof k.parameters === "object"
443
- ? k.parameters
444
- : { type: "object", properties: {}, additionalProperties: true };
445
- if (!k.parameters || typeof k.parameters !== "object") {
446
- this.logger.warn(`Memoria tool "${k.name}" has invalid/missing parameters; defaulting to permissive schema`);
447
- }
448
- // Register only tools with explicit response templates.
449
- // Knowledge-only records are treated as non-executable metadata.
450
- const hasResponseTemplate = typeof k.responseTemplate === "string" && k.responseTemplate.trim().length > 0;
451
- if (!hasResponseTemplate) {
452
- this.logger.warn(`Skipping Memoria tool "${k.name}" without executable responseTemplate`);
453
- continue;
454
- }
455
- const declaredRiskLevel = typeof k.riskLevel === "string" && validRiskLevels.has(k.riskLevel)
456
- ? k.riskLevel
457
- : "moderate";
458
- const isBashTemplate = typeof k.responseTemplate === "string" && isShellTemplate(k.responseTemplate);
459
- // Memoria bash templates execute shell commands and must always
460
- // flow through dangerous-risk approval paths.
461
- const riskLevel = isBashTemplate ? "dangerous" : declaredRiskLevel;
462
- const issues = normalizeDiscoveryIssues(k.failures);
463
- const toolDef = {
464
- description: k.description,
465
- category,
466
- parameters,
467
- riskLevel,
468
- };
469
- if (issues && issues.length > 0)
470
- toolDef.issues = issues;
471
- // Pass through executor-relevant fields from the tool definition.
472
- if (typeof k.responseTemplate === "string")
473
- toolDef.responseTemplate = k.responseTemplate;
474
- const tool = {
475
- name: k.name,
476
- description: k.description,
477
- category: category,
478
- parameters: parameters,
479
- riskLevel: riskLevel,
480
- issues,
481
- loadingTier: "deferred",
482
- execute: this.createMemoriaToolExecutor(k.name, toolDef),
483
- };
484
- if (this.tools.has(tool.name)) {
485
- // Don't override core tools with Memoria-discovered tools
486
- continue;
487
- }
488
- this.register(tool);
489
- count++;
490
- }
491
- catch {
492
- // Skip invalid tool definitions
493
- this.logger.warn(`Invalid tool definition in Memoria: ${k.name}`);
494
- }
495
- }
496
- offset += tools.length;
497
- if (tools.length < DISCOVERY_PAGE_SIZE)
498
- break;
499
- }
500
- if (pagesLoaded >= MAX_DISCOVERY_PAGES) {
501
- this.logger.warn(`Stopped Memoria discovery after ${MAX_DISCOVERY_PAGES} pages to avoid unbounded paging`);
502
- }
503
- }
504
- catch (error) {
505
- this.logger.error("Error discovering tools from Memoria:", error);
506
- }
507
- return count;
508
- }
509
- /**
510
- * Save a tool definition to Memoria
511
- *
512
- * @param name - Name of the tool to save
513
- * @param memoria - Memoria instance to save to
514
- */
515
- async saveToMemoria(name, memoria) {
516
- const tool = this.get(name);
517
- if (!tool) {
518
- throw new Error(`Tool "${name}" not found`);
519
- }
520
- const issues = Array.isArray(tool.issues) && tool.issues.length > 0
521
- ? tool.issues
522
- .map((issue) => (typeof issue === "string" ? issue.trim() : ""))
523
- .filter(Boolean)
524
- : [];
525
- const failures = issues.length > 0
526
- ? issues.map((issue) => ({
527
- timestamp: new Date(),
528
- error: issue,
529
- }))
530
- : undefined;
531
- const rawResponseTemplate = tool.responseTemplate;
532
- const responseTemplate = typeof rawResponseTemplate === "string" && rawResponseTemplate.trim().length > 0
533
- ? rawResponseTemplate
534
- : undefined;
535
- const memoriaRecord = {
536
- name: tool.name,
537
- description: tool.description,
538
- category: tool.category,
539
- parameters: tool.parameters,
540
- riskLevel: tool.riskLevel,
541
- ...(failures ? { failures } : {}),
542
- };
543
- if (responseTemplate) {
544
- memoriaRecord.responseTemplate = responseTemplate;
545
- }
546
- await memoria.rememberTool(memoriaRecord);
547
- }
548
- /**
549
- * Create an executor for a tool loaded from Memoria.
550
- *
551
- * Supports template execution only (no eval, no arbitrary code):
552
- *
553
- * **Response template**: If `toolDef.responseTemplate` is a string,
554
- * `{{key}}` placeholders are replaced with values from the input object.
555
- *
556
- * Input is validated against the full declared JSON Schema before interpolation.
557
- */
558
- createMemoriaToolExecutor(name, toolDef) {
559
- return async (input, _context) => {
560
- try {
561
- const inputObj = (input && typeof input === "object" ? input : {});
562
- // Validate full input schema if one is present.
563
- const params = toolDef.parameters;
564
- if (params && typeof params === "object") {
565
- const validationError = validateToolInput(input, params);
566
- if (validationError) {
567
- return {
568
- success: false,
569
- message: `Invalid input for tool "${name}": ${validationError}`,
570
- };
571
- }
572
- }
573
- // Template mode with {{key}} interpolation
574
- if (typeof toolDef.responseTemplate === "string") {
575
- const template = toolDef.responseTemplate;
576
- const isBashTemplate = isShellTemplate(template);
577
- const rendered = renderTemplate(template, inputObj, {
578
- escapeForShell: isBashTemplate,
579
- });
580
- if (isBashTemplate) {
581
- const bashPrefixIndex = rendered.toLowerCase().indexOf("bash:");
582
- const command = rendered.slice(bashPrefixIndex + "bash:".length).trim();
583
- if (!command) {
584
- return {
585
- success: false,
586
- message: `Tool "${name}" has empty bash command template`,
587
- };
588
- }
589
- const bashResult = await executeBash({
590
- command,
591
- cwd: _context.workingDir,
592
- env: _context.env,
593
- timeout: 30_000,
594
- }, _context);
595
- if (!bashResult.success) {
596
- return {
597
- success: false,
598
- message: `Tool "${name}" command failed: ${bashResult.message}`,
599
- };
600
- }
601
- const shellData = bashResult.data && typeof bashResult.data === "object"
602
- ? bashResult.data
603
- : undefined;
604
- const output = [shellData?.stdout, shellData?.stderr].filter(Boolean).join("\n").trim();
605
- return {
606
- success: true,
607
- message: `Tool "${name}" executed command template.`,
608
- data: output,
609
- };
610
- }
611
- return {
612
- success: true,
613
- message: `Tool "${name}" executed with template response.`,
614
- data: rendered,
615
- };
616
- }
617
- // No executable content at all
618
- return {
619
- success: false,
620
- message: `Tool "${name}" was loaded from Memoria but has no executable content. ` +
621
- `Add a "responseTemplate" field to the tool definition.`,
622
- };
623
- }
624
- catch (error) {
625
- return {
626
- success: false,
627
- message: `Tool "${name}" execution failed: ${error instanceof Error ? error.message : String(error)}`,
628
- };
629
- }
630
- };
631
- }
632
- /**
633
- * Connect to MCP servers and register their tools
634
- */
635
- async connectMCP(configs) {
636
- if (this.mcpClient) {
637
- await this.disconnectMCP();
638
- }
639
- this.mcpClient = new MCPClient({ logger: this.logger });
640
- // Connect to all servers
641
- const results = await Promise.allSettled(configs.map((c) => this.mcpClient.connect(c)));
642
- const failures = results
643
- .map((r, i) => (r.status === "rejected" ? configs[i].name : null))
644
- .filter(Boolean);
645
- if (failures.length > 0) {
646
- this.logger.warn(`Failed to connect MCP servers: ${failures.join(", ")}`);
647
- }
648
- // Register MCP tools
649
- const mcpTools = await this.mcpClient.listAllTools();
650
- for (const tool of mcpTools) {
651
- const isReadOnly = isMcpToolReadOnly(tool);
652
- this.register({
653
- name: `mcp__${tool.server}__${tool.name}`,
654
- description: tool.description || `MCP tool from ${tool.server}`,
655
- category: "meta",
656
- riskLevel: "moderate",
657
- isReadOnly,
658
- loadingTier: "always",
659
- parameters: tool.inputSchema,
660
- execute: async (args, ctx) => {
661
- if (ctx.abortSignal?.aborted) {
662
- return { success: false, message: "Cancelled" };
663
- }
664
- return this.mcpClient.callTool(tool.server, tool.name, args, ctx.abortSignal);
665
- },
666
- });
667
- }
668
- // Listen for tool list changes
669
- this.mcpClient.on("toolsChanged", async (serverName) => {
670
- try {
671
- await this.refreshMCPTools(serverName);
672
- }
673
- catch (error) {
674
- this.logger.error(`Failed to refresh MCP tools for ${serverName}:`, error.message);
675
- }
676
- });
677
- const resources = await this.mcpClient.listAllResources();
678
- const prompts = await this.mcpClient.listAllPrompts();
679
- return {
680
- tools: mcpTools.length,
681
- resources: resources.length,
682
- prompts: prompts.length,
683
- };
684
- }
685
- /**
686
- * Refresh tools from a specific MCP server
687
- */
688
- async refreshMCPTools(serverName) {
689
- // Remove existing tools from this server
690
- const prefix = `mcp__${serverName}__`;
691
- for (const name of this.tools.keys()) {
692
- if (name.startsWith(prefix)) {
693
- this.unregister(name);
694
- }
695
- }
696
- // Re-register tools
697
- const tools = await this.mcpClient.listAllTools();
698
- const serverTools = tools.filter((t) => t.server === serverName);
699
- for (const tool of serverTools) {
700
- const isReadOnly = isMcpToolReadOnly(tool);
701
- this.register({
702
- name: `mcp__${tool.server}__${tool.name}`,
703
- description: tool.description || `MCP tool from ${tool.server}`,
704
- category: "meta",
705
- riskLevel: "moderate",
706
- isReadOnly,
707
- loadingTier: "always",
708
- parameters: tool.inputSchema,
709
- execute: async (args, ctx) => {
710
- if (ctx.abortSignal?.aborted) {
711
- return { success: false, message: "Cancelled" };
712
- }
713
- return this.mcpClient.callTool(tool.server, tool.name, args, ctx.abortSignal);
714
- },
715
- });
716
- }
717
- }
718
- /**
719
- * Disconnect from all MCP servers
720
- */
721
- async disconnectMCP() {
722
- // Remove all MCP tools
723
- for (const name of this.tools.keys()) {
724
- if (name.startsWith("mcp__")) {
725
- this.unregister(name);
726
- }
727
- }
728
- await this.mcpClient?.disconnectAll();
729
- this.mcpClient = undefined;
730
- }
731
- /**
732
- * Get the MCP client for direct resource/prompt access
733
- */
734
- getMCPClient() {
735
- return this.mcpClient;
736
- }
737
- /**
738
- * Dispose of the registry, cleaning up all MCP connections.
739
- *
740
- * Shuts down every MCP server connection and clears all registered tools.
741
- * Safe to call multiple times (idempotent).
742
- */
743
- async dispose() {
744
- await this.disconnectMCP();
745
- this.tools.clear();
746
- }
747
- /**
748
- * Async disposable support for `await using registry = new ToolRegistry()`.
749
- *
750
- * Delegates to dispose() so that ToolRegistry works with the Explicit
751
- * Resource Management proposal (using/await using).
752
- */
753
- async [Symbol.asyncDispose]() {
754
- await this.dispose();
755
- }
756
- }