@agentforge/core 0.16.17 → 0.16.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -991,6 +991,79 @@ function emitRegistryEvent(eventHandlers, event, data) {
991
991
  });
992
992
  }
993
993
 
994
+ // src/tools/registry-mutations.ts
995
+ function eraseToolType(tool) {
996
+ return tool;
997
+ }
998
+ function registerRegistryTool(tools, tool, emit, events) {
999
+ const name = tool.metadata.name;
1000
+ if (tools.has(name)) {
1001
+ throw new Error(
1002
+ `Tool with name "${name}" is already registered. Use update() to modify it.`
1003
+ );
1004
+ }
1005
+ tools.set(name, eraseToolType(tool));
1006
+ emit(events.registered, tool);
1007
+ }
1008
+ function removeRegistryTool(tools, name, emit, events) {
1009
+ const tool = tools.get(name);
1010
+ if (!tool) {
1011
+ return false;
1012
+ }
1013
+ tools.delete(name);
1014
+ emit(events.removed, tool);
1015
+ return true;
1016
+ }
1017
+ function updateRegistryTool(tools, name, tool, emit, events) {
1018
+ if (!tools.has(name)) {
1019
+ return false;
1020
+ }
1021
+ if (tool.metadata.name !== name) {
1022
+ throw new Error(
1023
+ `Cannot update tool: metadata.name "${tool.metadata.name}" does not match registry key "${name}". To rename a tool, remove it and register it again with the new name.`
1024
+ );
1025
+ }
1026
+ tools.set(name, eraseToolType(tool));
1027
+ emit(events.updated, { name, tool });
1028
+ return true;
1029
+ }
1030
+ function registerManyRegistryTools(tools, toolsToRegister, emit, events) {
1031
+ const pendingTools = Array.from(toolsToRegister);
1032
+ const inputNames = /* @__PURE__ */ new Set();
1033
+ const duplicatesInInput = [];
1034
+ for (const tool of pendingTools) {
1035
+ const name = tool.metadata.name;
1036
+ if (inputNames.has(name)) {
1037
+ duplicatesInInput.push(name);
1038
+ } else {
1039
+ inputNames.add(name);
1040
+ }
1041
+ }
1042
+ if (duplicatesInInput.length > 0) {
1043
+ throw new Error(
1044
+ `Cannot register tools: duplicate names in input list: ${duplicatesInInput.join(", ")}`
1045
+ );
1046
+ }
1047
+ const conflicts = [];
1048
+ for (const tool of pendingTools) {
1049
+ if (tools.has(tool.metadata.name)) {
1050
+ conflicts.push(tool.metadata.name);
1051
+ }
1052
+ }
1053
+ if (conflicts.length > 0) {
1054
+ throw new Error(
1055
+ `Cannot register tools: the following names already exist: ${conflicts.join(", ")}`
1056
+ );
1057
+ }
1058
+ for (const tool of pendingTools) {
1059
+ registerRegistryTool(tools, tool, emit, events);
1060
+ }
1061
+ }
1062
+ function clearRegistryTools(tools, emit, events) {
1063
+ tools.clear();
1064
+ emit(events.cleared, null);
1065
+ }
1066
+
994
1067
  // src/tools/registry-prompt.ts
995
1068
  var import_zod3 = require("zod");
996
1069
 
@@ -1261,33 +1334,31 @@ var RegistryEvent = /* @__PURE__ */ ((RegistryEvent2) => {
1261
1334
  RegistryEvent2["REGISTRY_CLEARED"] = "registry:cleared";
1262
1335
  return RegistryEvent2;
1263
1336
  })(RegistryEvent || {});
1264
- function eraseToolType(tool) {
1265
- return tool;
1266
- }
1267
1337
  var ToolRegistry = class {
1268
1338
  tools = /* @__PURE__ */ new Map();
1269
1339
  eventHandlers = /* @__PURE__ */ new Map();
1340
+ mutationEvents = {
1341
+ registered: "tool:registered" /* TOOL_REGISTERED */,
1342
+ removed: "tool:removed" /* TOOL_REMOVED */,
1343
+ updated: "tool:updated" /* TOOL_UPDATED */,
1344
+ cleared: "registry:cleared" /* REGISTRY_CLEARED */
1345
+ };
1346
+ emitMutation = (event, data) => {
1347
+ this.emit(event, data);
1348
+ };
1270
1349
  /**
1271
1350
  * Register a tool in the registry
1272
1351
  *
1273
1352
  * @param tool - The tool to register
1274
1353
  * @throws Error if a tool with the same name already exists
1275
1354
  *
1276
- * @example
1277
- * ```ts
1278
- * registry.register(readFileTool);
1355
+ * @example
1356
+ * ```ts
1357
+ * registry.register(readFileTool);
1279
1358
  * ```
1280
1359
  */
1281
1360
  register(tool) {
1282
- const erasedTool = eraseToolType(tool);
1283
- const name = tool.metadata.name;
1284
- if (this.tools.has(name)) {
1285
- throw new Error(
1286
- `Tool with name "${name}" is already registered. Use update() to modify it.`
1287
- );
1288
- }
1289
- this.tools.set(name, erasedTool);
1290
- this.emit("tool:registered" /* TOOL_REGISTERED */, tool);
1361
+ registerRegistryTool(this.tools, tool, this.emitMutation, this.mutationEvents);
1291
1362
  }
1292
1363
  /**
1293
1364
  * Get a tool by name
@@ -1328,20 +1399,14 @@ var ToolRegistry = class {
1328
1399
  * @param name - The tool name
1329
1400
  * @returns True if the tool was removed, false if it didn't exist
1330
1401
  *
1331
- * @example
1332
- * ```ts
1333
- * const removed = registry.remove('read-file');
1334
- * console.log(removed ? 'Removed' : 'Not found');
1402
+ * @example
1403
+ * ```ts
1404
+ * const removed = registry.remove('read-file');
1405
+ * console.log(removed ? 'Removed' : 'Not found');
1335
1406
  * ```
1336
1407
  */
1337
1408
  remove(name) {
1338
- const tool = this.tools.get(name);
1339
- if (!tool) {
1340
- return false;
1341
- }
1342
- this.tools.delete(name);
1343
- this.emit("tool:removed" /* TOOL_REMOVED */, tool);
1344
- return true;
1409
+ return removeRegistryTool(this.tools, name, this.emitMutation, this.mutationEvents);
1345
1410
  }
1346
1411
  /**
1347
1412
  * Update an existing tool
@@ -1351,24 +1416,13 @@ var ToolRegistry = class {
1351
1416
  * @returns True if updated, false if the tool didn't exist
1352
1417
  * @throws Error if the tool's metadata.name doesn't match the name parameter
1353
1418
  *
1354
- * @example
1355
- * ```ts
1356
- * const updated = registry.update('read-file', newReadFileTool);
1419
+ * @example
1420
+ * ```ts
1421
+ * const updated = registry.update('read-file', newReadFileTool);
1357
1422
  * ```
1358
1423
  */
1359
1424
  update(name, tool) {
1360
- const erasedTool = eraseToolType(tool);
1361
- if (!this.tools.has(name)) {
1362
- return false;
1363
- }
1364
- if (tool.metadata.name !== name) {
1365
- throw new Error(
1366
- `Cannot update tool: metadata.name "${tool.metadata.name}" does not match registry key "${name}". To rename a tool, remove it and register it again with the new name.`
1367
- );
1368
- }
1369
- this.tools.set(name, erasedTool);
1370
- this.emit("tool:updated" /* TOOL_UPDATED */, { name, tool });
1371
- return true;
1425
+ return updateRegistryTool(this.tools, name, tool, this.emitMutation, this.mutationEvents);
1372
1426
  }
1373
1427
  /**
1374
1428
  * Get all registered tools
@@ -1435,55 +1489,25 @@ var ToolRegistry = class {
1435
1489
  * @param tools - Iterable of tools to register
1436
1490
  * @throws Error if any tool name conflicts with existing tools
1437
1491
  *
1438
- * @example
1439
- * ```ts
1440
- * registry.registerMany([tool1, tool2, tool3]);
1492
+ * @example
1493
+ * ```ts
1494
+ * registry.registerMany([tool1, tool2, tool3]);
1441
1495
  * ```
1442
1496
  */
1443
1497
  registerMany(tools) {
1444
- const toolsToRegister = Array.from(tools);
1445
- const inputNames = /* @__PURE__ */ new Set();
1446
- const duplicatesInInput = [];
1447
- for (const tool of toolsToRegister) {
1448
- const name = tool.metadata.name;
1449
- if (inputNames.has(name)) {
1450
- duplicatesInInput.push(name);
1451
- } else {
1452
- inputNames.add(name);
1453
- }
1454
- }
1455
- if (duplicatesInInput.length > 0) {
1456
- throw new Error(
1457
- `Cannot register tools: duplicate names in input list: ${duplicatesInInput.join(", ")}`
1458
- );
1459
- }
1460
- const conflicts = [];
1461
- for (const tool of toolsToRegister) {
1462
- if (this.tools.has(tool.metadata.name)) {
1463
- conflicts.push(tool.metadata.name);
1464
- }
1465
- }
1466
- if (conflicts.length > 0) {
1467
- throw new Error(
1468
- `Cannot register tools: the following names already exist: ${conflicts.join(", ")}`
1469
- );
1470
- }
1471
- for (const tool of toolsToRegister) {
1472
- this.register(tool);
1473
- }
1498
+ registerManyRegistryTools(this.tools, tools, this.emitMutation, this.mutationEvents);
1474
1499
  }
1475
1500
  /**
1476
1501
  * Clear all tools from the registry
1477
1502
  *
1478
- * @example
1479
- * ```ts
1480
- * registry.clear();
1481
- * console.log(registry.size()); // 0
1503
+ * @example
1504
+ * ```ts
1505
+ * registry.clear();
1506
+ * console.log(registry.size()); // 0
1482
1507
  * ```
1483
1508
  */
1484
1509
  clear() {
1485
- this.tools.clear();
1486
- this.emit("registry:cleared" /* REGISTRY_CLEARED */, null);
1510
+ clearRegistryTools(this.tools, this.emitMutation, this.mutationEvents);
1487
1511
  }
1488
1512
  /**
1489
1513
  * Get the number of registered tools
@@ -1822,6 +1846,12 @@ var ManagedTool = class {
1822
1846
  failedExecutions: 0
1823
1847
  };
1824
1848
  _healthCheckTimer;
1849
+ _beforeExitHandler;
1850
+ _healthCheckInFlight = false;
1851
+ _cleaningUp = false;
1852
+ _cleanupPromise;
1853
+ _initializePromise;
1854
+ _lifecycleGeneration = 0;
1825
1855
  constructor(config) {
1826
1856
  this.name = config.name;
1827
1857
  this.description = config.description;
@@ -1832,17 +1862,7 @@ var ManagedTool = class {
1832
1862
  this.autoCleanup = config.autoCleanup ?? true;
1833
1863
  this.healthCheckInterval = config.healthCheckInterval;
1834
1864
  this._context = config.context;
1835
- if (this.autoCleanup) {
1836
- process.on("beforeExit", () => {
1837
- this.cleanup().catch(
1838
- (err) => logger3.error("Cleanup failed", {
1839
- toolName: this.name,
1840
- error: err instanceof Error ? err.message : String(err),
1841
- ...err instanceof Error && err.stack ? { stack: err.stack } : {}
1842
- })
1843
- );
1844
- });
1845
- }
1865
+ this.ensureBeforeExitHandler();
1846
1866
  }
1847
1867
  /**
1848
1868
  * Get the tool context (e.g., connection pool, API client)
@@ -1866,28 +1886,26 @@ var ManagedTool = class {
1866
1886
  * Initialize the tool
1867
1887
  */
1868
1888
  async initialize() {
1869
- if (this._initialized) {
1889
+ if (this._initializePromise) {
1890
+ await this._initializePromise;
1870
1891
  return;
1871
1892
  }
1872
- if (this.initializeFn) {
1873
- await this.initializeFn();
1893
+ if (this._cleanupPromise) {
1894
+ await this._cleanupPromise;
1895
+ if (this._initializePromise) {
1896
+ await this._initializePromise;
1897
+ return;
1898
+ }
1874
1899
  }
1875
- this._initialized = true;
1876
- this._stats.initialized = true;
1877
- if (this.healthCheckInterval && this.healthCheckFn) {
1878
- this._healthCheckTimer = setInterval(async () => {
1879
- try {
1880
- const result = await this.healthCheckFn();
1881
- this._stats.lastHealthCheck = result;
1882
- this._stats.lastHealthCheckTime = Date.now();
1883
- } catch (error) {
1884
- this._stats.lastHealthCheck = {
1885
- healthy: false,
1886
- error: error.message
1887
- };
1888
- this._stats.lastHealthCheckTime = Date.now();
1889
- }
1890
- }, this.healthCheckInterval);
1900
+ if (this._initialized) {
1901
+ return;
1902
+ }
1903
+ const initializePromise = this.performInitialize();
1904
+ this._initializePromise = initializePromise;
1905
+ try {
1906
+ await initializePromise;
1907
+ } finally {
1908
+ this._initializePromise = void 0;
1891
1909
  }
1892
1910
  }
1893
1911
  /**
@@ -1914,24 +1932,55 @@ var ManagedTool = class {
1914
1932
  * Cleanup the tool
1915
1933
  */
1916
1934
  async cleanup() {
1917
- if (!this._initialized) {
1935
+ if (this._cleanupPromise) {
1936
+ await this._cleanupPromise;
1918
1937
  return;
1919
1938
  }
1939
+ if (this._initializePromise) {
1940
+ try {
1941
+ await this._initializePromise;
1942
+ } catch {
1943
+ }
1944
+ }
1945
+ const cleanupPromise = this.performCleanup();
1946
+ this._cleanupPromise = cleanupPromise;
1947
+ try {
1948
+ await cleanupPromise;
1949
+ } finally {
1950
+ this._cleanupPromise = void 0;
1951
+ }
1952
+ }
1953
+ async performCleanup() {
1954
+ this._cleaningUp = true;
1920
1955
  if (this._healthCheckTimer) {
1921
1956
  clearInterval(this._healthCheckTimer);
1922
1957
  this._healthCheckTimer = void 0;
1923
1958
  }
1924
- if (this.cleanupFn) {
1925
- await this.cleanupFn();
1959
+ if (this._beforeExitHandler) {
1960
+ process.off("beforeExit", this._beforeExitHandler);
1961
+ this._beforeExitHandler = void 0;
1962
+ }
1963
+ if (!this._initialized) {
1964
+ this._cleaningUp = false;
1965
+ return;
1926
1966
  }
1927
1967
  this._initialized = false;
1928
1968
  this._stats.initialized = false;
1969
+ if (this.cleanupFn) {
1970
+ try {
1971
+ await this.cleanupFn();
1972
+ } finally {
1973
+ this._cleaningUp = false;
1974
+ }
1975
+ return;
1976
+ }
1977
+ this._cleaningUp = false;
1929
1978
  }
1930
1979
  /**
1931
1980
  * Run health check
1932
1981
  */
1933
1982
  async healthCheck() {
1934
- if (!this._initialized) {
1983
+ if (!this._initialized || this._cleaningUp) {
1935
1984
  return {
1936
1985
  healthy: false,
1937
1986
  error: "Tool is not initialized"
@@ -1943,15 +1992,28 @@ var ManagedTool = class {
1943
1992
  metadata: { message: "No health check configured" }
1944
1993
  };
1945
1994
  }
1995
+ const lifecycleGeneration = this._lifecycleGeneration;
1946
1996
  try {
1947
1997
  const result = await this.healthCheckFn();
1998
+ if (!this._initialized || this._cleaningUp || this._lifecycleGeneration !== lifecycleGeneration) {
1999
+ return {
2000
+ healthy: false,
2001
+ error: "Tool is not initialized"
2002
+ };
2003
+ }
1948
2004
  this._stats.lastHealthCheck = result;
1949
2005
  this._stats.lastHealthCheckTime = Date.now();
1950
2006
  return result;
1951
2007
  } catch (error) {
2008
+ if (!this._initialized || this._cleaningUp || this._lifecycleGeneration !== lifecycleGeneration) {
2009
+ return {
2010
+ healthy: false,
2011
+ error: "Tool is not initialized"
2012
+ };
2013
+ }
1952
2014
  const result = {
1953
2015
  healthy: false,
1954
- error: error.message
2016
+ error: getErrorMessage(error)
1955
2017
  };
1956
2018
  this._stats.lastHealthCheck = result;
1957
2019
  this._stats.lastHealthCheckTime = Date.now();
@@ -1985,10 +2047,68 @@ var ManagedTool = class {
1985
2047
  }
1986
2048
  };
1987
2049
  }
2050
+ async runPeriodicHealthCheck() {
2051
+ if (!this.healthCheckFn || this._healthCheckInFlight || !this._initialized || this._cleaningUp) {
2052
+ return;
2053
+ }
2054
+ this._healthCheckInFlight = true;
2055
+ const lifecycleGeneration = this._lifecycleGeneration;
2056
+ try {
2057
+ const result = await this.healthCheckFn();
2058
+ if (!this._initialized || this._cleaningUp || this._lifecycleGeneration !== lifecycleGeneration) {
2059
+ return;
2060
+ }
2061
+ this._stats.lastHealthCheck = result;
2062
+ this._stats.lastHealthCheckTime = Date.now();
2063
+ } catch (error) {
2064
+ if (!this._initialized || this._cleaningUp || this._lifecycleGeneration !== lifecycleGeneration) {
2065
+ return;
2066
+ }
2067
+ this._stats.lastHealthCheck = {
2068
+ healthy: false,
2069
+ error: getErrorMessage(error)
2070
+ };
2071
+ this._stats.lastHealthCheckTime = Date.now();
2072
+ } finally {
2073
+ this._healthCheckInFlight = false;
2074
+ }
2075
+ }
2076
+ async performInitialize() {
2077
+ this.ensureBeforeExitHandler();
2078
+ this._lifecycleGeneration++;
2079
+ if (this.initializeFn) {
2080
+ await this.initializeFn();
2081
+ }
2082
+ this._initialized = true;
2083
+ this._stats.initialized = true;
2084
+ if (this.healthCheckInterval && this.healthCheckFn) {
2085
+ this._healthCheckTimer = setInterval(() => {
2086
+ void this.runPeriodicHealthCheck();
2087
+ }, this.healthCheckInterval);
2088
+ }
2089
+ }
2090
+ ensureBeforeExitHandler() {
2091
+ if (!this.autoCleanup || this._beforeExitHandler) {
2092
+ return;
2093
+ }
2094
+ this._beforeExitHandler = () => {
2095
+ this.cleanup().catch(
2096
+ (err) => logger3.error("Cleanup failed", {
2097
+ toolName: this.name,
2098
+ error: getErrorMessage(err),
2099
+ ...err instanceof Error && err.stack ? { stack: err.stack } : {}
2100
+ })
2101
+ );
2102
+ };
2103
+ process.on("beforeExit", this._beforeExitHandler);
2104
+ }
1988
2105
  };
1989
2106
  function createManagedTool(config) {
1990
2107
  return new ManagedTool(config);
1991
2108
  }
2109
+ function getErrorMessage(error) {
2110
+ return error instanceof Error ? error.message : String(error);
2111
+ }
1992
2112
 
1993
2113
  // src/tools/composition.ts
1994
2114
  function isConditionalStep(step) {
package/dist/index.d.cts CHANGED
@@ -1257,15 +1257,17 @@ interface PromptOptions extends RegistryPromptOptions {
1257
1257
  declare class ToolRegistry {
1258
1258
  private tools;
1259
1259
  private eventHandlers;
1260
+ private readonly mutationEvents;
1261
+ private readonly emitMutation;
1260
1262
  /**
1261
1263
  * Register a tool in the registry
1262
1264
  *
1263
1265
  * @param tool - The tool to register
1264
1266
  * @throws Error if a tool with the same name already exists
1265
1267
  *
1266
- * @example
1267
- * ```ts
1268
- * registry.register(readFileTool);
1268
+ * @example
1269
+ * ```ts
1270
+ * registry.register(readFileTool);
1269
1271
  * ```
1270
1272
  */
1271
1273
  register<TInput, TOutput>(tool: Tool<TInput, TOutput>): void;
@@ -1304,10 +1306,10 @@ declare class ToolRegistry {
1304
1306
  * @param name - The tool name
1305
1307
  * @returns True if the tool was removed, false if it didn't exist
1306
1308
  *
1307
- * @example
1308
- * ```ts
1309
- * const removed = registry.remove('read-file');
1310
- * console.log(removed ? 'Removed' : 'Not found');
1309
+ * @example
1310
+ * ```ts
1311
+ * const removed = registry.remove('read-file');
1312
+ * console.log(removed ? 'Removed' : 'Not found');
1311
1313
  * ```
1312
1314
  */
1313
1315
  remove(name: string): boolean;
@@ -1319,9 +1321,9 @@ declare class ToolRegistry {
1319
1321
  * @returns True if updated, false if the tool didn't exist
1320
1322
  * @throws Error if the tool's metadata.name doesn't match the name parameter
1321
1323
  *
1322
- * @example
1323
- * ```ts
1324
- * const updated = registry.update('read-file', newReadFileTool);
1324
+ * @example
1325
+ * ```ts
1326
+ * const updated = registry.update('read-file', newReadFileTool);
1325
1327
  * ```
1326
1328
  */
1327
1329
  update<TInput, TOutput>(name: string, tool: Tool<TInput, TOutput>): boolean;
@@ -1382,19 +1384,19 @@ declare class ToolRegistry {
1382
1384
  * @param tools - Iterable of tools to register
1383
1385
  * @throws Error if any tool name conflicts with existing tools
1384
1386
  *
1385
- * @example
1386
- * ```ts
1387
- * registry.registerMany([tool1, tool2, tool3]);
1387
+ * @example
1388
+ * ```ts
1389
+ * registry.registerMany([tool1, tool2, tool3]);
1388
1390
  * ```
1389
1391
  */
1390
1392
  registerMany(tools: Iterable<RegisterManyTool>): void;
1391
1393
  /**
1392
1394
  * Clear all tools from the registry
1393
1395
  *
1394
- * @example
1395
- * ```ts
1396
- * registry.clear();
1397
- * console.log(registry.size()); // 0
1396
+ * @example
1397
+ * ```ts
1398
+ * registry.clear();
1399
+ * console.log(registry.size()); // 0
1398
1400
  * ```
1399
1401
  */
1400
1402
  clear(): void;
@@ -1568,26 +1570,38 @@ declare function createToolExecutor(config?: ToolExecutorConfig): {
1568
1570
  };
1569
1571
  };
1570
1572
 
1573
+ /**
1574
+ * Shared JSON-safe payload contracts for observability and monitoring paths.
1575
+ */
1576
+ type JsonPrimitive = string | number | boolean | null;
1577
+ type JsonValue = JsonPrimitive | JsonObject | JsonValue[];
1578
+ interface JsonObject {
1579
+ [key: string]: JsonValue;
1580
+ }
1581
+
1571
1582
  /**
1572
1583
  * Tool Lifecycle Management - Manage tool initialization, cleanup, and resources
1573
1584
  * @module tools/lifecycle
1574
1585
  */
1586
+
1575
1587
  interface ToolHealthCheckResult {
1576
1588
  healthy: boolean;
1577
1589
  error?: string;
1578
- metadata?: Record<string, any>;
1590
+ metadata?: JsonObject;
1579
1591
  }
1580
- interface ManagedToolConfig<TContext = any, TInput = any, TOutput = any> {
1592
+ interface ManagedToolConfigBase<TContext, TInput, TOutput> {
1581
1593
  name: string;
1582
1594
  description: string;
1583
1595
  initialize?: (this: ManagedTool<TContext, TInput, TOutput>) => Promise<void>;
1584
1596
  execute: (this: ManagedTool<TContext, TInput, TOutput>, input: TInput) => Promise<TOutput>;
1585
1597
  cleanup?: (this: ManagedTool<TContext, TInput, TOutput>) => Promise<void>;
1586
1598
  healthCheck?: (this: ManagedTool<TContext, TInput, TOutput>) => Promise<ToolHealthCheckResult>;
1587
- context?: TContext;
1588
1599
  autoCleanup?: boolean;
1589
1600
  healthCheckInterval?: number;
1590
1601
  }
1602
+ interface ManagedToolConfig<TContext = undefined, TInput = unknown, TOutput = unknown> extends ManagedToolConfigBase<TContext, TInput, TOutput> {
1603
+ context?: TContext;
1604
+ }
1591
1605
  interface ManagedToolStats {
1592
1606
  initialized: boolean;
1593
1607
  totalExecutions: number;
@@ -1600,7 +1614,7 @@ interface ManagedToolStats {
1600
1614
  /**
1601
1615
  * Managed tool with lifecycle hooks
1602
1616
  */
1603
- declare class ManagedTool<TContext = any, TInput = any, TOutput = any> {
1617
+ declare class ManagedTool<TContext = undefined, TInput = unknown, TOutput = unknown> {
1604
1618
  readonly name: string;
1605
1619
  readonly description: string;
1606
1620
  private readonly initializeFn?;
@@ -1613,15 +1627,21 @@ declare class ManagedTool<TContext = any, TInput = any, TOutput = any> {
1613
1627
  private _context;
1614
1628
  private _stats;
1615
1629
  private _healthCheckTimer?;
1630
+ private _beforeExitHandler?;
1631
+ private _healthCheckInFlight;
1632
+ private _cleaningUp;
1633
+ private _cleanupPromise?;
1634
+ private _initializePromise?;
1635
+ private _lifecycleGeneration;
1616
1636
  constructor(config: ManagedToolConfig<TContext, TInput, TOutput>);
1617
1637
  /**
1618
1638
  * Get the tool context (e.g., connection pool, API client)
1619
1639
  */
1620
- get context(): TContext;
1640
+ get context(): TContext | undefined;
1621
1641
  /**
1622
1642
  * Set the tool context
1623
1643
  */
1624
- set context(value: TContext);
1644
+ set context(value: TContext | undefined);
1625
1645
  /**
1626
1646
  * Check if tool is initialized
1627
1647
  */
@@ -1638,6 +1658,7 @@ declare class ManagedTool<TContext = any, TInput = any, TOutput = any> {
1638
1658
  * Cleanup the tool
1639
1659
  */
1640
1660
  cleanup(): Promise<void>;
1661
+ private performCleanup;
1641
1662
  /**
1642
1663
  * Run health check
1643
1664
  */
@@ -1658,11 +1679,14 @@ declare class ManagedTool<TContext = any, TInput = any, TOutput = any> {
1658
1679
  description: string;
1659
1680
  invoke: (input: TInput) => Promise<TOutput>;
1660
1681
  };
1682
+ private runPeriodicHealthCheck;
1683
+ private performInitialize;
1684
+ private ensureBeforeExitHandler;
1661
1685
  }
1662
1686
  /**
1663
1687
  * Create a managed tool
1664
1688
  */
1665
- declare function createManagedTool<TContext = any, TInput = any, TOutput = any>(config: ManagedToolConfig<TContext, TInput, TOutput>): ManagedTool<TContext, TInput, TOutput>;
1689
+ declare function createManagedTool<TContext = undefined, TInput = unknown, TOutput = unknown>(config: ManagedToolConfig<TContext, TInput, TOutput>): ManagedTool<TContext, TInput, TOutput>;
1666
1690
 
1667
1691
  /**
1668
1692
  * Tool Composition - Compose tools into higher-level operations
@@ -2813,15 +2837,6 @@ interface ErrorHandlerOptions<State> {
2813
2837
  */
2814
2838
  declare function withErrorHandler<State>(node: (state: State) => State | Promise<State> | Partial<State> | Promise<Partial<State>>, options: ErrorHandlerOptions<State>): (state: State) => Promise<State | Partial<State>>;
2815
2839
 
2816
- /**
2817
- * Shared JSON-safe payload contracts for observability and monitoring paths.
2818
- */
2819
- type JsonPrimitive = string | number | boolean | null;
2820
- type JsonValue = JsonPrimitive | JsonObject | JsonValue[];
2821
- interface JsonObject {
2822
- [key: string]: JsonValue;
2823
- }
2824
-
2825
2840
  /**
2826
2841
  * Structured Logging Utilities
2827
2842
  *
package/dist/index.d.ts CHANGED
@@ -1257,15 +1257,17 @@ interface PromptOptions extends RegistryPromptOptions {
1257
1257
  declare class ToolRegistry {
1258
1258
  private tools;
1259
1259
  private eventHandlers;
1260
+ private readonly mutationEvents;
1261
+ private readonly emitMutation;
1260
1262
  /**
1261
1263
  * Register a tool in the registry
1262
1264
  *
1263
1265
  * @param tool - The tool to register
1264
1266
  * @throws Error if a tool with the same name already exists
1265
1267
  *
1266
- * @example
1267
- * ```ts
1268
- * registry.register(readFileTool);
1268
+ * @example
1269
+ * ```ts
1270
+ * registry.register(readFileTool);
1269
1271
  * ```
1270
1272
  */
1271
1273
  register<TInput, TOutput>(tool: Tool<TInput, TOutput>): void;
@@ -1304,10 +1306,10 @@ declare class ToolRegistry {
1304
1306
  * @param name - The tool name
1305
1307
  * @returns True if the tool was removed, false if it didn't exist
1306
1308
  *
1307
- * @example
1308
- * ```ts
1309
- * const removed = registry.remove('read-file');
1310
- * console.log(removed ? 'Removed' : 'Not found');
1309
+ * @example
1310
+ * ```ts
1311
+ * const removed = registry.remove('read-file');
1312
+ * console.log(removed ? 'Removed' : 'Not found');
1311
1313
  * ```
1312
1314
  */
1313
1315
  remove(name: string): boolean;
@@ -1319,9 +1321,9 @@ declare class ToolRegistry {
1319
1321
  * @returns True if updated, false if the tool didn't exist
1320
1322
  * @throws Error if the tool's metadata.name doesn't match the name parameter
1321
1323
  *
1322
- * @example
1323
- * ```ts
1324
- * const updated = registry.update('read-file', newReadFileTool);
1324
+ * @example
1325
+ * ```ts
1326
+ * const updated = registry.update('read-file', newReadFileTool);
1325
1327
  * ```
1326
1328
  */
1327
1329
  update<TInput, TOutput>(name: string, tool: Tool<TInput, TOutput>): boolean;
@@ -1382,19 +1384,19 @@ declare class ToolRegistry {
1382
1384
  * @param tools - Iterable of tools to register
1383
1385
  * @throws Error if any tool name conflicts with existing tools
1384
1386
  *
1385
- * @example
1386
- * ```ts
1387
- * registry.registerMany([tool1, tool2, tool3]);
1387
+ * @example
1388
+ * ```ts
1389
+ * registry.registerMany([tool1, tool2, tool3]);
1388
1390
  * ```
1389
1391
  */
1390
1392
  registerMany(tools: Iterable<RegisterManyTool>): void;
1391
1393
  /**
1392
1394
  * Clear all tools from the registry
1393
1395
  *
1394
- * @example
1395
- * ```ts
1396
- * registry.clear();
1397
- * console.log(registry.size()); // 0
1396
+ * @example
1397
+ * ```ts
1398
+ * registry.clear();
1399
+ * console.log(registry.size()); // 0
1398
1400
  * ```
1399
1401
  */
1400
1402
  clear(): void;
@@ -1568,26 +1570,38 @@ declare function createToolExecutor(config?: ToolExecutorConfig): {
1568
1570
  };
1569
1571
  };
1570
1572
 
1573
+ /**
1574
+ * Shared JSON-safe payload contracts for observability and monitoring paths.
1575
+ */
1576
+ type JsonPrimitive = string | number | boolean | null;
1577
+ type JsonValue = JsonPrimitive | JsonObject | JsonValue[];
1578
+ interface JsonObject {
1579
+ [key: string]: JsonValue;
1580
+ }
1581
+
1571
1582
  /**
1572
1583
  * Tool Lifecycle Management - Manage tool initialization, cleanup, and resources
1573
1584
  * @module tools/lifecycle
1574
1585
  */
1586
+
1575
1587
  interface ToolHealthCheckResult {
1576
1588
  healthy: boolean;
1577
1589
  error?: string;
1578
- metadata?: Record<string, any>;
1590
+ metadata?: JsonObject;
1579
1591
  }
1580
- interface ManagedToolConfig<TContext = any, TInput = any, TOutput = any> {
1592
+ interface ManagedToolConfigBase<TContext, TInput, TOutput> {
1581
1593
  name: string;
1582
1594
  description: string;
1583
1595
  initialize?: (this: ManagedTool<TContext, TInput, TOutput>) => Promise<void>;
1584
1596
  execute: (this: ManagedTool<TContext, TInput, TOutput>, input: TInput) => Promise<TOutput>;
1585
1597
  cleanup?: (this: ManagedTool<TContext, TInput, TOutput>) => Promise<void>;
1586
1598
  healthCheck?: (this: ManagedTool<TContext, TInput, TOutput>) => Promise<ToolHealthCheckResult>;
1587
- context?: TContext;
1588
1599
  autoCleanup?: boolean;
1589
1600
  healthCheckInterval?: number;
1590
1601
  }
1602
+ interface ManagedToolConfig<TContext = undefined, TInput = unknown, TOutput = unknown> extends ManagedToolConfigBase<TContext, TInput, TOutput> {
1603
+ context?: TContext;
1604
+ }
1591
1605
  interface ManagedToolStats {
1592
1606
  initialized: boolean;
1593
1607
  totalExecutions: number;
@@ -1600,7 +1614,7 @@ interface ManagedToolStats {
1600
1614
  /**
1601
1615
  * Managed tool with lifecycle hooks
1602
1616
  */
1603
- declare class ManagedTool<TContext = any, TInput = any, TOutput = any> {
1617
+ declare class ManagedTool<TContext = undefined, TInput = unknown, TOutput = unknown> {
1604
1618
  readonly name: string;
1605
1619
  readonly description: string;
1606
1620
  private readonly initializeFn?;
@@ -1613,15 +1627,21 @@ declare class ManagedTool<TContext = any, TInput = any, TOutput = any> {
1613
1627
  private _context;
1614
1628
  private _stats;
1615
1629
  private _healthCheckTimer?;
1630
+ private _beforeExitHandler?;
1631
+ private _healthCheckInFlight;
1632
+ private _cleaningUp;
1633
+ private _cleanupPromise?;
1634
+ private _initializePromise?;
1635
+ private _lifecycleGeneration;
1616
1636
  constructor(config: ManagedToolConfig<TContext, TInput, TOutput>);
1617
1637
  /**
1618
1638
  * Get the tool context (e.g., connection pool, API client)
1619
1639
  */
1620
- get context(): TContext;
1640
+ get context(): TContext | undefined;
1621
1641
  /**
1622
1642
  * Set the tool context
1623
1643
  */
1624
- set context(value: TContext);
1644
+ set context(value: TContext | undefined);
1625
1645
  /**
1626
1646
  * Check if tool is initialized
1627
1647
  */
@@ -1638,6 +1658,7 @@ declare class ManagedTool<TContext = any, TInput = any, TOutput = any> {
1638
1658
  * Cleanup the tool
1639
1659
  */
1640
1660
  cleanup(): Promise<void>;
1661
+ private performCleanup;
1641
1662
  /**
1642
1663
  * Run health check
1643
1664
  */
@@ -1658,11 +1679,14 @@ declare class ManagedTool<TContext = any, TInput = any, TOutput = any> {
1658
1679
  description: string;
1659
1680
  invoke: (input: TInput) => Promise<TOutput>;
1660
1681
  };
1682
+ private runPeriodicHealthCheck;
1683
+ private performInitialize;
1684
+ private ensureBeforeExitHandler;
1661
1685
  }
1662
1686
  /**
1663
1687
  * Create a managed tool
1664
1688
  */
1665
- declare function createManagedTool<TContext = any, TInput = any, TOutput = any>(config: ManagedToolConfig<TContext, TInput, TOutput>): ManagedTool<TContext, TInput, TOutput>;
1689
+ declare function createManagedTool<TContext = undefined, TInput = unknown, TOutput = unknown>(config: ManagedToolConfig<TContext, TInput, TOutput>): ManagedTool<TContext, TInput, TOutput>;
1666
1690
 
1667
1691
  /**
1668
1692
  * Tool Composition - Compose tools into higher-level operations
@@ -2813,15 +2837,6 @@ interface ErrorHandlerOptions<State> {
2813
2837
  */
2814
2838
  declare function withErrorHandler<State>(node: (state: State) => State | Promise<State> | Partial<State> | Promise<Partial<State>>, options: ErrorHandlerOptions<State>): (state: State) => Promise<State | Partial<State>>;
2815
2839
 
2816
- /**
2817
- * Shared JSON-safe payload contracts for observability and monitoring paths.
2818
- */
2819
- type JsonPrimitive = string | number | boolean | null;
2820
- type JsonValue = JsonPrimitive | JsonObject | JsonValue[];
2821
- interface JsonObject {
2822
- [key: string]: JsonValue;
2823
- }
2824
-
2825
2840
  /**
2826
2841
  * Structured Logging Utilities
2827
2842
  *
package/dist/index.js CHANGED
@@ -816,6 +816,79 @@ function emitRegistryEvent(eventHandlers, event, data) {
816
816
  });
817
817
  }
818
818
 
819
+ // src/tools/registry-mutations.ts
820
+ function eraseToolType(tool) {
821
+ return tool;
822
+ }
823
+ function registerRegistryTool(tools, tool, emit, events) {
824
+ const name = tool.metadata.name;
825
+ if (tools.has(name)) {
826
+ throw new Error(
827
+ `Tool with name "${name}" is already registered. Use update() to modify it.`
828
+ );
829
+ }
830
+ tools.set(name, eraseToolType(tool));
831
+ emit(events.registered, tool);
832
+ }
833
+ function removeRegistryTool(tools, name, emit, events) {
834
+ const tool = tools.get(name);
835
+ if (!tool) {
836
+ return false;
837
+ }
838
+ tools.delete(name);
839
+ emit(events.removed, tool);
840
+ return true;
841
+ }
842
+ function updateRegistryTool(tools, name, tool, emit, events) {
843
+ if (!tools.has(name)) {
844
+ return false;
845
+ }
846
+ if (tool.metadata.name !== name) {
847
+ throw new Error(
848
+ `Cannot update tool: metadata.name "${tool.metadata.name}" does not match registry key "${name}". To rename a tool, remove it and register it again with the new name.`
849
+ );
850
+ }
851
+ tools.set(name, eraseToolType(tool));
852
+ emit(events.updated, { name, tool });
853
+ return true;
854
+ }
855
+ function registerManyRegistryTools(tools, toolsToRegister, emit, events) {
856
+ const pendingTools = Array.from(toolsToRegister);
857
+ const inputNames = /* @__PURE__ */ new Set();
858
+ const duplicatesInInput = [];
859
+ for (const tool of pendingTools) {
860
+ const name = tool.metadata.name;
861
+ if (inputNames.has(name)) {
862
+ duplicatesInInput.push(name);
863
+ } else {
864
+ inputNames.add(name);
865
+ }
866
+ }
867
+ if (duplicatesInInput.length > 0) {
868
+ throw new Error(
869
+ `Cannot register tools: duplicate names in input list: ${duplicatesInInput.join(", ")}`
870
+ );
871
+ }
872
+ const conflicts = [];
873
+ for (const tool of pendingTools) {
874
+ if (tools.has(tool.metadata.name)) {
875
+ conflicts.push(tool.metadata.name);
876
+ }
877
+ }
878
+ if (conflicts.length > 0) {
879
+ throw new Error(
880
+ `Cannot register tools: the following names already exist: ${conflicts.join(", ")}`
881
+ );
882
+ }
883
+ for (const tool of pendingTools) {
884
+ registerRegistryTool(tools, tool, emit, events);
885
+ }
886
+ }
887
+ function clearRegistryTools(tools, emit, events) {
888
+ tools.clear();
889
+ emit(events.cleared, null);
890
+ }
891
+
819
892
  // src/tools/registry-prompt.ts
820
893
  import { z as z3 } from "zod";
821
894
 
@@ -1086,33 +1159,31 @@ var RegistryEvent = /* @__PURE__ */ ((RegistryEvent2) => {
1086
1159
  RegistryEvent2["REGISTRY_CLEARED"] = "registry:cleared";
1087
1160
  return RegistryEvent2;
1088
1161
  })(RegistryEvent || {});
1089
- function eraseToolType(tool) {
1090
- return tool;
1091
- }
1092
1162
  var ToolRegistry = class {
1093
1163
  tools = /* @__PURE__ */ new Map();
1094
1164
  eventHandlers = /* @__PURE__ */ new Map();
1165
+ mutationEvents = {
1166
+ registered: "tool:registered" /* TOOL_REGISTERED */,
1167
+ removed: "tool:removed" /* TOOL_REMOVED */,
1168
+ updated: "tool:updated" /* TOOL_UPDATED */,
1169
+ cleared: "registry:cleared" /* REGISTRY_CLEARED */
1170
+ };
1171
+ emitMutation = (event, data) => {
1172
+ this.emit(event, data);
1173
+ };
1095
1174
  /**
1096
1175
  * Register a tool in the registry
1097
1176
  *
1098
1177
  * @param tool - The tool to register
1099
1178
  * @throws Error if a tool with the same name already exists
1100
1179
  *
1101
- * @example
1102
- * ```ts
1103
- * registry.register(readFileTool);
1180
+ * @example
1181
+ * ```ts
1182
+ * registry.register(readFileTool);
1104
1183
  * ```
1105
1184
  */
1106
1185
  register(tool) {
1107
- const erasedTool = eraseToolType(tool);
1108
- const name = tool.metadata.name;
1109
- if (this.tools.has(name)) {
1110
- throw new Error(
1111
- `Tool with name "${name}" is already registered. Use update() to modify it.`
1112
- );
1113
- }
1114
- this.tools.set(name, erasedTool);
1115
- this.emit("tool:registered" /* TOOL_REGISTERED */, tool);
1186
+ registerRegistryTool(this.tools, tool, this.emitMutation, this.mutationEvents);
1116
1187
  }
1117
1188
  /**
1118
1189
  * Get a tool by name
@@ -1153,20 +1224,14 @@ var ToolRegistry = class {
1153
1224
  * @param name - The tool name
1154
1225
  * @returns True if the tool was removed, false if it didn't exist
1155
1226
  *
1156
- * @example
1157
- * ```ts
1158
- * const removed = registry.remove('read-file');
1159
- * console.log(removed ? 'Removed' : 'Not found');
1227
+ * @example
1228
+ * ```ts
1229
+ * const removed = registry.remove('read-file');
1230
+ * console.log(removed ? 'Removed' : 'Not found');
1160
1231
  * ```
1161
1232
  */
1162
1233
  remove(name) {
1163
- const tool = this.tools.get(name);
1164
- if (!tool) {
1165
- return false;
1166
- }
1167
- this.tools.delete(name);
1168
- this.emit("tool:removed" /* TOOL_REMOVED */, tool);
1169
- return true;
1234
+ return removeRegistryTool(this.tools, name, this.emitMutation, this.mutationEvents);
1170
1235
  }
1171
1236
  /**
1172
1237
  * Update an existing tool
@@ -1176,24 +1241,13 @@ var ToolRegistry = class {
1176
1241
  * @returns True if updated, false if the tool didn't exist
1177
1242
  * @throws Error if the tool's metadata.name doesn't match the name parameter
1178
1243
  *
1179
- * @example
1180
- * ```ts
1181
- * const updated = registry.update('read-file', newReadFileTool);
1244
+ * @example
1245
+ * ```ts
1246
+ * const updated = registry.update('read-file', newReadFileTool);
1182
1247
  * ```
1183
1248
  */
1184
1249
  update(name, tool) {
1185
- const erasedTool = eraseToolType(tool);
1186
- if (!this.tools.has(name)) {
1187
- return false;
1188
- }
1189
- if (tool.metadata.name !== name) {
1190
- throw new Error(
1191
- `Cannot update tool: metadata.name "${tool.metadata.name}" does not match registry key "${name}". To rename a tool, remove it and register it again with the new name.`
1192
- );
1193
- }
1194
- this.tools.set(name, erasedTool);
1195
- this.emit("tool:updated" /* TOOL_UPDATED */, { name, tool });
1196
- return true;
1250
+ return updateRegistryTool(this.tools, name, tool, this.emitMutation, this.mutationEvents);
1197
1251
  }
1198
1252
  /**
1199
1253
  * Get all registered tools
@@ -1260,55 +1314,25 @@ var ToolRegistry = class {
1260
1314
  * @param tools - Iterable of tools to register
1261
1315
  * @throws Error if any tool name conflicts with existing tools
1262
1316
  *
1263
- * @example
1264
- * ```ts
1265
- * registry.registerMany([tool1, tool2, tool3]);
1317
+ * @example
1318
+ * ```ts
1319
+ * registry.registerMany([tool1, tool2, tool3]);
1266
1320
  * ```
1267
1321
  */
1268
1322
  registerMany(tools) {
1269
- const toolsToRegister = Array.from(tools);
1270
- const inputNames = /* @__PURE__ */ new Set();
1271
- const duplicatesInInput = [];
1272
- for (const tool of toolsToRegister) {
1273
- const name = tool.metadata.name;
1274
- if (inputNames.has(name)) {
1275
- duplicatesInInput.push(name);
1276
- } else {
1277
- inputNames.add(name);
1278
- }
1279
- }
1280
- if (duplicatesInInput.length > 0) {
1281
- throw new Error(
1282
- `Cannot register tools: duplicate names in input list: ${duplicatesInInput.join(", ")}`
1283
- );
1284
- }
1285
- const conflicts = [];
1286
- for (const tool of toolsToRegister) {
1287
- if (this.tools.has(tool.metadata.name)) {
1288
- conflicts.push(tool.metadata.name);
1289
- }
1290
- }
1291
- if (conflicts.length > 0) {
1292
- throw new Error(
1293
- `Cannot register tools: the following names already exist: ${conflicts.join(", ")}`
1294
- );
1295
- }
1296
- for (const tool of toolsToRegister) {
1297
- this.register(tool);
1298
- }
1323
+ registerManyRegistryTools(this.tools, tools, this.emitMutation, this.mutationEvents);
1299
1324
  }
1300
1325
  /**
1301
1326
  * Clear all tools from the registry
1302
1327
  *
1303
- * @example
1304
- * ```ts
1305
- * registry.clear();
1306
- * console.log(registry.size()); // 0
1328
+ * @example
1329
+ * ```ts
1330
+ * registry.clear();
1331
+ * console.log(registry.size()); // 0
1307
1332
  * ```
1308
1333
  */
1309
1334
  clear() {
1310
- this.tools.clear();
1311
- this.emit("registry:cleared" /* REGISTRY_CLEARED */, null);
1335
+ clearRegistryTools(this.tools, this.emitMutation, this.mutationEvents);
1312
1336
  }
1313
1337
  /**
1314
1338
  * Get the number of registered tools
@@ -1647,6 +1671,12 @@ var ManagedTool = class {
1647
1671
  failedExecutions: 0
1648
1672
  };
1649
1673
  _healthCheckTimer;
1674
+ _beforeExitHandler;
1675
+ _healthCheckInFlight = false;
1676
+ _cleaningUp = false;
1677
+ _cleanupPromise;
1678
+ _initializePromise;
1679
+ _lifecycleGeneration = 0;
1650
1680
  constructor(config) {
1651
1681
  this.name = config.name;
1652
1682
  this.description = config.description;
@@ -1657,17 +1687,7 @@ var ManagedTool = class {
1657
1687
  this.autoCleanup = config.autoCleanup ?? true;
1658
1688
  this.healthCheckInterval = config.healthCheckInterval;
1659
1689
  this._context = config.context;
1660
- if (this.autoCleanup) {
1661
- process.on("beforeExit", () => {
1662
- this.cleanup().catch(
1663
- (err) => logger3.error("Cleanup failed", {
1664
- toolName: this.name,
1665
- error: err instanceof Error ? err.message : String(err),
1666
- ...err instanceof Error && err.stack ? { stack: err.stack } : {}
1667
- })
1668
- );
1669
- });
1670
- }
1690
+ this.ensureBeforeExitHandler();
1671
1691
  }
1672
1692
  /**
1673
1693
  * Get the tool context (e.g., connection pool, API client)
@@ -1691,28 +1711,26 @@ var ManagedTool = class {
1691
1711
  * Initialize the tool
1692
1712
  */
1693
1713
  async initialize() {
1694
- if (this._initialized) {
1714
+ if (this._initializePromise) {
1715
+ await this._initializePromise;
1695
1716
  return;
1696
1717
  }
1697
- if (this.initializeFn) {
1698
- await this.initializeFn();
1718
+ if (this._cleanupPromise) {
1719
+ await this._cleanupPromise;
1720
+ if (this._initializePromise) {
1721
+ await this._initializePromise;
1722
+ return;
1723
+ }
1699
1724
  }
1700
- this._initialized = true;
1701
- this._stats.initialized = true;
1702
- if (this.healthCheckInterval && this.healthCheckFn) {
1703
- this._healthCheckTimer = setInterval(async () => {
1704
- try {
1705
- const result = await this.healthCheckFn();
1706
- this._stats.lastHealthCheck = result;
1707
- this._stats.lastHealthCheckTime = Date.now();
1708
- } catch (error) {
1709
- this._stats.lastHealthCheck = {
1710
- healthy: false,
1711
- error: error.message
1712
- };
1713
- this._stats.lastHealthCheckTime = Date.now();
1714
- }
1715
- }, this.healthCheckInterval);
1725
+ if (this._initialized) {
1726
+ return;
1727
+ }
1728
+ const initializePromise = this.performInitialize();
1729
+ this._initializePromise = initializePromise;
1730
+ try {
1731
+ await initializePromise;
1732
+ } finally {
1733
+ this._initializePromise = void 0;
1716
1734
  }
1717
1735
  }
1718
1736
  /**
@@ -1739,24 +1757,55 @@ var ManagedTool = class {
1739
1757
  * Cleanup the tool
1740
1758
  */
1741
1759
  async cleanup() {
1742
- if (!this._initialized) {
1760
+ if (this._cleanupPromise) {
1761
+ await this._cleanupPromise;
1743
1762
  return;
1744
1763
  }
1764
+ if (this._initializePromise) {
1765
+ try {
1766
+ await this._initializePromise;
1767
+ } catch {
1768
+ }
1769
+ }
1770
+ const cleanupPromise = this.performCleanup();
1771
+ this._cleanupPromise = cleanupPromise;
1772
+ try {
1773
+ await cleanupPromise;
1774
+ } finally {
1775
+ this._cleanupPromise = void 0;
1776
+ }
1777
+ }
1778
+ async performCleanup() {
1779
+ this._cleaningUp = true;
1745
1780
  if (this._healthCheckTimer) {
1746
1781
  clearInterval(this._healthCheckTimer);
1747
1782
  this._healthCheckTimer = void 0;
1748
1783
  }
1749
- if (this.cleanupFn) {
1750
- await this.cleanupFn();
1784
+ if (this._beforeExitHandler) {
1785
+ process.off("beforeExit", this._beforeExitHandler);
1786
+ this._beforeExitHandler = void 0;
1787
+ }
1788
+ if (!this._initialized) {
1789
+ this._cleaningUp = false;
1790
+ return;
1751
1791
  }
1752
1792
  this._initialized = false;
1753
1793
  this._stats.initialized = false;
1794
+ if (this.cleanupFn) {
1795
+ try {
1796
+ await this.cleanupFn();
1797
+ } finally {
1798
+ this._cleaningUp = false;
1799
+ }
1800
+ return;
1801
+ }
1802
+ this._cleaningUp = false;
1754
1803
  }
1755
1804
  /**
1756
1805
  * Run health check
1757
1806
  */
1758
1807
  async healthCheck() {
1759
- if (!this._initialized) {
1808
+ if (!this._initialized || this._cleaningUp) {
1760
1809
  return {
1761
1810
  healthy: false,
1762
1811
  error: "Tool is not initialized"
@@ -1768,15 +1817,28 @@ var ManagedTool = class {
1768
1817
  metadata: { message: "No health check configured" }
1769
1818
  };
1770
1819
  }
1820
+ const lifecycleGeneration = this._lifecycleGeneration;
1771
1821
  try {
1772
1822
  const result = await this.healthCheckFn();
1823
+ if (!this._initialized || this._cleaningUp || this._lifecycleGeneration !== lifecycleGeneration) {
1824
+ return {
1825
+ healthy: false,
1826
+ error: "Tool is not initialized"
1827
+ };
1828
+ }
1773
1829
  this._stats.lastHealthCheck = result;
1774
1830
  this._stats.lastHealthCheckTime = Date.now();
1775
1831
  return result;
1776
1832
  } catch (error) {
1833
+ if (!this._initialized || this._cleaningUp || this._lifecycleGeneration !== lifecycleGeneration) {
1834
+ return {
1835
+ healthy: false,
1836
+ error: "Tool is not initialized"
1837
+ };
1838
+ }
1777
1839
  const result = {
1778
1840
  healthy: false,
1779
- error: error.message
1841
+ error: getErrorMessage(error)
1780
1842
  };
1781
1843
  this._stats.lastHealthCheck = result;
1782
1844
  this._stats.lastHealthCheckTime = Date.now();
@@ -1810,10 +1872,68 @@ var ManagedTool = class {
1810
1872
  }
1811
1873
  };
1812
1874
  }
1875
+ async runPeriodicHealthCheck() {
1876
+ if (!this.healthCheckFn || this._healthCheckInFlight || !this._initialized || this._cleaningUp) {
1877
+ return;
1878
+ }
1879
+ this._healthCheckInFlight = true;
1880
+ const lifecycleGeneration = this._lifecycleGeneration;
1881
+ try {
1882
+ const result = await this.healthCheckFn();
1883
+ if (!this._initialized || this._cleaningUp || this._lifecycleGeneration !== lifecycleGeneration) {
1884
+ return;
1885
+ }
1886
+ this._stats.lastHealthCheck = result;
1887
+ this._stats.lastHealthCheckTime = Date.now();
1888
+ } catch (error) {
1889
+ if (!this._initialized || this._cleaningUp || this._lifecycleGeneration !== lifecycleGeneration) {
1890
+ return;
1891
+ }
1892
+ this._stats.lastHealthCheck = {
1893
+ healthy: false,
1894
+ error: getErrorMessage(error)
1895
+ };
1896
+ this._stats.lastHealthCheckTime = Date.now();
1897
+ } finally {
1898
+ this._healthCheckInFlight = false;
1899
+ }
1900
+ }
1901
+ async performInitialize() {
1902
+ this.ensureBeforeExitHandler();
1903
+ this._lifecycleGeneration++;
1904
+ if (this.initializeFn) {
1905
+ await this.initializeFn();
1906
+ }
1907
+ this._initialized = true;
1908
+ this._stats.initialized = true;
1909
+ if (this.healthCheckInterval && this.healthCheckFn) {
1910
+ this._healthCheckTimer = setInterval(() => {
1911
+ void this.runPeriodicHealthCheck();
1912
+ }, this.healthCheckInterval);
1913
+ }
1914
+ }
1915
+ ensureBeforeExitHandler() {
1916
+ if (!this.autoCleanup || this._beforeExitHandler) {
1917
+ return;
1918
+ }
1919
+ this._beforeExitHandler = () => {
1920
+ this.cleanup().catch(
1921
+ (err) => logger3.error("Cleanup failed", {
1922
+ toolName: this.name,
1923
+ error: getErrorMessage(err),
1924
+ ...err instanceof Error && err.stack ? { stack: err.stack } : {}
1925
+ })
1926
+ );
1927
+ };
1928
+ process.on("beforeExit", this._beforeExitHandler);
1929
+ }
1813
1930
  };
1814
1931
  function createManagedTool(config) {
1815
1932
  return new ManagedTool(config);
1816
1933
  }
1934
+ function getErrorMessage(error) {
1935
+ return error instanceof Error ? error.message : String(error);
1936
+ }
1817
1937
 
1818
1938
  // src/tools/composition.ts
1819
1939
  function isConditionalStep(step) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentforge/core",
3
- "version": "0.16.17",
3
+ "version": "0.16.19",
4
4
  "description": "Production-ready TypeScript agent framework built on LangGraph with orchestration, middleware, and typed abstractions.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",