@everworker/oneringai 0.4.3 → 0.4.5

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.js CHANGED
@@ -9960,15 +9960,16 @@ var init_PDFHandler = __esm({
9960
9960
  const unpdf = await getUnpdf();
9961
9961
  const pieces = [];
9962
9962
  let pieceIndex = 0;
9963
+ const data = new Uint8Array(buffer);
9963
9964
  let metadata = {};
9964
9965
  const includeMetadata = options.formatOptions?.pdf?.includeMetadata !== false;
9965
9966
  if (includeMetadata) {
9966
9967
  try {
9967
- metadata = await unpdf.getMeta(buffer);
9968
+ metadata = await unpdf.getMeta(data);
9968
9969
  } catch {
9969
9970
  }
9970
9971
  }
9971
- const textResult = await unpdf.extractText(buffer, { mergePages: false });
9972
+ const textResult = await unpdf.extractText(data, { mergePages: false });
9972
9973
  const pages = textResult?.pages || textResult?.text ? Array.isArray(textResult.text) ? textResult.text : [textResult.text] : [];
9973
9974
  const requestedPages = options.pages;
9974
9975
  const pageEntries = pages.map((text, i) => ({ text, pageNum: i + 1 }));
@@ -10021,7 +10022,7 @@ var init_PDFHandler = __esm({
10021
10022
  }
10022
10023
  if (options.extractImages !== false) {
10023
10024
  try {
10024
- const imagesResult = await unpdf.extractImages(buffer, {});
10025
+ const imagesResult = await unpdf.extractImages(data, {});
10025
10026
  const images = imagesResult?.images || [];
10026
10027
  for (const img of images) {
10027
10028
  if (!img.data) continue;
@@ -10254,6 +10255,475 @@ init_Connector();
10254
10255
  init_ScopedConnectorRegistry();
10255
10256
  init_StorageRegistry();
10256
10257
 
10258
+ // src/core/ToolCatalogRegistry.ts
10259
+ init_Logger();
10260
+ var ToolCatalogRegistry = class {
10261
+ /** Category definitions: name → definition */
10262
+ static _categories = /* @__PURE__ */ new Map();
10263
+ /** Tools per category: category name → tool entries */
10264
+ static _tools = /* @__PURE__ */ new Map();
10265
+ /** Whether built-in tools have been registered */
10266
+ static _initialized = false;
10267
+ /** Lazy-loaded ConnectorTools module. null = not attempted, false = failed */
10268
+ static _connectorToolsModule = null;
10269
+ // --- Built-in category descriptions ---
10270
+ static BUILTIN_DESCRIPTIONS = {
10271
+ filesystem: "Read, write, edit, search, and list files and directories",
10272
+ shell: "Execute bash/shell commands",
10273
+ web: "Fetch and process web content",
10274
+ code: "Execute JavaScript code in sandboxed VM",
10275
+ json: "Parse, query, and transform JSON data",
10276
+ desktop: "Screenshot, mouse, keyboard, and window desktop automation",
10277
+ "custom-tools": "Create, save, load, and test custom tool definitions",
10278
+ routines: "Generate and manage agent routines",
10279
+ other: "Miscellaneous tools"
10280
+ };
10281
+ // ========================================================================
10282
+ // Static Helpers (DRY)
10283
+ // ========================================================================
10284
+ /**
10285
+ * Convert a hyphenated or plain name to a display name.
10286
+ * E.g., 'custom-tools' → 'Custom Tools', 'filesystem' → 'Filesystem'
10287
+ */
10288
+ static toDisplayName(name) {
10289
+ return name.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
10290
+ }
10291
+ /**
10292
+ * Parse a connector category name, returning the connector name or null.
10293
+ * E.g., 'connector:github' → 'github', 'filesystem' → null
10294
+ */
10295
+ static parseConnectorCategory(category) {
10296
+ return category.startsWith("connector:") ? category.slice("connector:".length) : null;
10297
+ }
10298
+ /**
10299
+ * Get the ConnectorTools module (lazy-loaded, cached).
10300
+ * Returns null if ConnectorTools is not available.
10301
+ * Uses false sentinel to prevent retrying after first failure.
10302
+ *
10303
+ * NOTE: The dynamic require() path fails in bundled environments (Meteor, Webpack).
10304
+ * Call setConnectorToolsModule() at app startup to inject the module explicitly.
10305
+ */
10306
+ static getConnectorToolsModule() {
10307
+ if (this._connectorToolsModule === null) {
10308
+ try {
10309
+ this._connectorToolsModule = __require("../../tools/connector/ConnectorTools.js");
10310
+ } catch {
10311
+ this._connectorToolsModule = false;
10312
+ }
10313
+ }
10314
+ return this._connectorToolsModule || null;
10315
+ }
10316
+ /**
10317
+ * Explicitly set the ConnectorTools module reference.
10318
+ *
10319
+ * Use this in bundled environments (Meteor, Webpack, etc.) where the lazy
10320
+ * require('../../tools/connector/ConnectorTools.js') fails due to path resolution.
10321
+ *
10322
+ * @example
10323
+ * ```typescript
10324
+ * import { ToolCatalogRegistry, ConnectorTools } from '@everworker/oneringai';
10325
+ * ToolCatalogRegistry.setConnectorToolsModule({ ConnectorTools });
10326
+ * ```
10327
+ */
10328
+ static setConnectorToolsModule(mod) {
10329
+ this._connectorToolsModule = mod;
10330
+ }
10331
+ // ========================================================================
10332
+ // Registration
10333
+ // ========================================================================
10334
+ /**
10335
+ * Register a tool category.
10336
+ * If the category already exists, updates its metadata.
10337
+ * @throws Error if name is empty or whitespace
10338
+ */
10339
+ static registerCategory(def) {
10340
+ if (!def.name || !def.name.trim()) {
10341
+ throw new Error("[ToolCatalogRegistry] Category name cannot be empty");
10342
+ }
10343
+ this._categories.set(def.name, def);
10344
+ if (!this._tools.has(def.name)) {
10345
+ this._tools.set(def.name, []);
10346
+ }
10347
+ }
10348
+ /**
10349
+ * Register multiple tools in a category.
10350
+ * The category is auto-created if it doesn't exist (with a generic description).
10351
+ * @throws Error if category name is empty or whitespace
10352
+ */
10353
+ static registerTools(category, tools) {
10354
+ if (!category || !category.trim()) {
10355
+ throw new Error("[ToolCatalogRegistry] Category name cannot be empty");
10356
+ }
10357
+ if (!this._categories.has(category)) {
10358
+ this._categories.set(category, {
10359
+ name: category,
10360
+ displayName: this.toDisplayName(category),
10361
+ description: this.BUILTIN_DESCRIPTIONS[category] ?? `Tools in the ${category} category`
10362
+ });
10363
+ }
10364
+ const existing = this._tools.get(category) ?? [];
10365
+ const existingNames = new Set(existing.map((t) => t.name));
10366
+ const newTools = tools.filter((t) => !existingNames.has(t.name));
10367
+ const updated = existing.map((t) => {
10368
+ const replacement = tools.find((nt) => nt.name === t.name);
10369
+ return replacement ?? t;
10370
+ });
10371
+ this._tools.set(category, [...updated, ...newTools]);
10372
+ }
10373
+ /**
10374
+ * Register a single tool in a category.
10375
+ */
10376
+ static registerTool(category, tool) {
10377
+ this.registerTools(category, [tool]);
10378
+ }
10379
+ /**
10380
+ * Unregister a category and all its tools.
10381
+ */
10382
+ static unregisterCategory(category) {
10383
+ const hadCategory = this._categories.delete(category);
10384
+ this._tools.delete(category);
10385
+ return hadCategory;
10386
+ }
10387
+ /**
10388
+ * Unregister a single tool from a category.
10389
+ */
10390
+ static unregisterTool(category, toolName) {
10391
+ const tools = this._tools.get(category);
10392
+ if (!tools) return false;
10393
+ const idx = tools.findIndex((t) => t.name === toolName);
10394
+ if (idx === -1) return false;
10395
+ tools.splice(idx, 1);
10396
+ return true;
10397
+ }
10398
+ // ========================================================================
10399
+ // Query
10400
+ // ========================================================================
10401
+ /**
10402
+ * Get all registered categories.
10403
+ */
10404
+ static getCategories() {
10405
+ this.ensureInitialized();
10406
+ return Array.from(this._categories.values());
10407
+ }
10408
+ /**
10409
+ * Get a single category by name.
10410
+ */
10411
+ static getCategory(name) {
10412
+ this.ensureInitialized();
10413
+ return this._categories.get(name);
10414
+ }
10415
+ /**
10416
+ * Check if a category exists.
10417
+ */
10418
+ static hasCategory(name) {
10419
+ this.ensureInitialized();
10420
+ return this._categories.has(name);
10421
+ }
10422
+ /**
10423
+ * Get all tools in a category.
10424
+ */
10425
+ static getToolsInCategory(category) {
10426
+ this.ensureInitialized();
10427
+ return this._tools.get(category) ?? [];
10428
+ }
10429
+ /**
10430
+ * Get all catalog tools across all categories.
10431
+ */
10432
+ static getAllCatalogTools() {
10433
+ this.ensureInitialized();
10434
+ const all = [];
10435
+ for (const tools of this._tools.values()) {
10436
+ all.push(...tools);
10437
+ }
10438
+ return all;
10439
+ }
10440
+ /**
10441
+ * Find a tool by name across all categories.
10442
+ */
10443
+ static findTool(name) {
10444
+ this.ensureInitialized();
10445
+ for (const [category, tools] of this._tools) {
10446
+ const entry = tools.find((t) => t.name === name);
10447
+ if (entry) return { category, entry };
10448
+ }
10449
+ return void 0;
10450
+ }
10451
+ // ========================================================================
10452
+ // Filtering
10453
+ // ========================================================================
10454
+ /**
10455
+ * Filter categories by scope.
10456
+ */
10457
+ static filterCategories(scope) {
10458
+ const all = this.getCategories();
10459
+ if (!scope) return all;
10460
+ return all.filter((cat) => this.isCategoryAllowed(cat.name, scope));
10461
+ }
10462
+ /**
10463
+ * Check if a category is allowed by a scope.
10464
+ */
10465
+ static isCategoryAllowed(name, scope) {
10466
+ if (!scope) return true;
10467
+ if (Array.isArray(scope)) {
10468
+ return scope.includes(name);
10469
+ }
10470
+ if ("include" in scope) {
10471
+ return scope.include.includes(name);
10472
+ }
10473
+ if ("exclude" in scope) {
10474
+ return !scope.exclude.includes(name);
10475
+ }
10476
+ return true;
10477
+ }
10478
+ // ========================================================================
10479
+ // Connector Discovery
10480
+ // ========================================================================
10481
+ /**
10482
+ * Discover all connector categories with their tools.
10483
+ * Calls ConnectorTools.discoverAll() and filters by scope/identities.
10484
+ *
10485
+ * @param options - Optional filtering
10486
+ * @returns Array of connector category info
10487
+ */
10488
+ static discoverConnectorCategories(options) {
10489
+ const mod = this.getConnectorToolsModule();
10490
+ if (!mod) return [];
10491
+ try {
10492
+ const discovered = mod.ConnectorTools.discoverAll();
10493
+ const results = [];
10494
+ for (const [connectorName, tools] of discovered) {
10495
+ const catName = `connector:${connectorName}`;
10496
+ if (options?.scope && !this.isCategoryAllowed(catName, options.scope)) {
10497
+ continue;
10498
+ }
10499
+ if (options?.identities?.length) {
10500
+ const hasIdentity = options.identities.some((id) => id.connector === connectorName);
10501
+ if (!hasIdentity) continue;
10502
+ }
10503
+ const preRegistered = this.getCategory(catName);
10504
+ results.push({
10505
+ name: catName,
10506
+ displayName: preRegistered?.displayName ?? this.toDisplayName(connectorName),
10507
+ description: preRegistered?.description ?? `API tools for ${connectorName}`,
10508
+ toolCount: tools.length,
10509
+ tools
10510
+ });
10511
+ }
10512
+ return results;
10513
+ } catch {
10514
+ return [];
10515
+ }
10516
+ }
10517
+ /**
10518
+ * Resolve tools for a specific connector category.
10519
+ *
10520
+ * @param category - Category name in 'connector:<name>' format
10521
+ * @returns Array of resolved tools with names
10522
+ */
10523
+ static resolveConnectorCategoryTools(category) {
10524
+ const connectorName = this.parseConnectorCategory(category);
10525
+ if (!connectorName) return [];
10526
+ const mod = this.getConnectorToolsModule();
10527
+ if (!mod) return [];
10528
+ try {
10529
+ const tools = mod.ConnectorTools.for(connectorName);
10530
+ return tools.map((t) => ({ tool: t, name: t.definition.function.name }));
10531
+ } catch {
10532
+ return [];
10533
+ }
10534
+ }
10535
+ // ========================================================================
10536
+ // Resolution
10537
+ // ========================================================================
10538
+ /**
10539
+ * Resolve tool names to ToolFunction[].
10540
+ *
10541
+ * Searches registered categories and (optionally) connector tools.
10542
+ * Used by app-level executors (e.g., V25's OneRingAgentExecutor).
10543
+ *
10544
+ * @param toolNames - Array of tool names to resolve
10545
+ * @param options - Resolution options
10546
+ * @returns Resolved tool functions (skips unresolvable names with warning)
10547
+ */
10548
+ static resolveTools(toolNames, options) {
10549
+ this.ensureInitialized();
10550
+ const resolved = [];
10551
+ const missing = [];
10552
+ for (const name of toolNames) {
10553
+ const found = this.findTool(name);
10554
+ if (found) {
10555
+ const tool = this.resolveEntryTool(found.entry, options?.context);
10556
+ if (tool) {
10557
+ resolved.push(tool);
10558
+ continue;
10559
+ }
10560
+ }
10561
+ if (options?.includeConnectors) {
10562
+ const connectorTool = this.findConnectorTool(name);
10563
+ if (connectorTool) {
10564
+ resolved.push(connectorTool);
10565
+ continue;
10566
+ }
10567
+ }
10568
+ missing.push(name);
10569
+ }
10570
+ if (missing.length > 0) {
10571
+ logger.warn(
10572
+ { missing, resolved: resolved.length, total: toolNames.length },
10573
+ `[ToolCatalogRegistry.resolveTools] Could not resolve ${missing.length} tool(s): ${missing.join(", ")}`
10574
+ );
10575
+ }
10576
+ return resolved;
10577
+ }
10578
+ /**
10579
+ * Resolve tools grouped by connector name.
10580
+ *
10581
+ * Tools with a `connectorName` go into `byConnector`; all others go into `plain`.
10582
+ * Supports factory-based tool creation via `createTool` when context is provided.
10583
+ *
10584
+ * @param toolNames - Array of tool names to resolve
10585
+ * @param context - Optional context passed to createTool factories
10586
+ * @param options - Resolution options
10587
+ * @returns Grouped tools: plain + byConnector map
10588
+ */
10589
+ static resolveToolsGrouped(toolNames, context, options) {
10590
+ this.ensureInitialized();
10591
+ const plain = [];
10592
+ const byConnector = /* @__PURE__ */ new Map();
10593
+ for (const name of toolNames) {
10594
+ const found = this.findTool(name);
10595
+ if (found) {
10596
+ const entry = found.entry;
10597
+ const tool = this.resolveEntryTool(entry, context);
10598
+ if (!tool) continue;
10599
+ if (entry.connectorName) {
10600
+ const list = byConnector.get(entry.connectorName) ?? [];
10601
+ list.push(tool);
10602
+ byConnector.set(entry.connectorName, list);
10603
+ } else {
10604
+ plain.push(tool);
10605
+ }
10606
+ continue;
10607
+ }
10608
+ if (options?.includeConnectors) {
10609
+ const connectorTool = this.findConnectorTool(name);
10610
+ if (connectorTool) {
10611
+ plain.push(connectorTool);
10612
+ continue;
10613
+ }
10614
+ }
10615
+ }
10616
+ return { plain, byConnector };
10617
+ }
10618
+ /**
10619
+ * Resolve a tool from a CatalogToolEntry, using factory if available.
10620
+ * Returns null if neither tool nor createTool is available.
10621
+ */
10622
+ static resolveEntryTool(entry, context) {
10623
+ if (entry.createTool && context) {
10624
+ try {
10625
+ return entry.createTool(context);
10626
+ } catch (e) {
10627
+ logger.warn(`[ToolCatalogRegistry] Factory failed for '${entry.name}': ${e}`);
10628
+ return null;
10629
+ }
10630
+ }
10631
+ return entry.tool ?? null;
10632
+ }
10633
+ /**
10634
+ * Search connector tools by name (uses lazy accessor).
10635
+ */
10636
+ static findConnectorTool(name) {
10637
+ const mod = this.getConnectorToolsModule();
10638
+ if (!mod) return void 0;
10639
+ try {
10640
+ const allConnectorTools = mod.ConnectorTools.discoverAll();
10641
+ for (const [, tools] of allConnectorTools) {
10642
+ for (const tool of tools) {
10643
+ if (tool.definition.function.name === name) {
10644
+ return tool;
10645
+ }
10646
+ }
10647
+ }
10648
+ } catch {
10649
+ }
10650
+ return void 0;
10651
+ }
10652
+ // ========================================================================
10653
+ // Built-in initialization
10654
+ // ========================================================================
10655
+ /**
10656
+ * Ensure built-in tools from registry.generated.ts are registered.
10657
+ * Called lazily on first query.
10658
+ *
10659
+ * In ESM environments, call `initializeFromRegistry(toolRegistry)` explicitly
10660
+ * from your app startup instead of relying on auto-initialization.
10661
+ */
10662
+ static ensureInitialized() {
10663
+ if (this._initialized) return;
10664
+ this._initialized = true;
10665
+ try {
10666
+ const mod = __require("../../tools/registry.generated.js");
10667
+ const registry = mod?.toolRegistry ?? mod?.default?.toolRegistry;
10668
+ if (Array.isArray(registry)) {
10669
+ this.registerFromToolRegistry(registry);
10670
+ }
10671
+ } catch {
10672
+ }
10673
+ }
10674
+ /**
10675
+ * Explicitly initialize from the generated tool registry.
10676
+ * Call this at app startup in ESM environments where lazy require() doesn't work.
10677
+ *
10678
+ * @example
10679
+ * ```typescript
10680
+ * import { toolRegistry } from './tools/registry.generated.js';
10681
+ * ToolCatalogRegistry.initializeFromRegistry(toolRegistry);
10682
+ * ```
10683
+ */
10684
+ static initializeFromRegistry(registry) {
10685
+ this._initialized = true;
10686
+ this.registerFromToolRegistry(registry);
10687
+ }
10688
+ /**
10689
+ * Internal: register tools from a tool registry array.
10690
+ */
10691
+ static registerFromToolRegistry(registry) {
10692
+ for (const entry of registry) {
10693
+ const category = entry.category;
10694
+ if (!this._categories.has(category)) {
10695
+ this.registerCategory({
10696
+ name: category,
10697
+ displayName: this.toDisplayName(category),
10698
+ description: this.BUILTIN_DESCRIPTIONS[category] ?? `Built-in ${category} tools`
10699
+ });
10700
+ }
10701
+ const catalogEntry = {
10702
+ tool: entry.tool,
10703
+ name: entry.name,
10704
+ displayName: entry.displayName,
10705
+ description: entry.description,
10706
+ safeByDefault: entry.safeByDefault,
10707
+ requiresConnector: entry.requiresConnector
10708
+ };
10709
+ const existing = this._tools.get(category) ?? [];
10710
+ if (!existing.some((t) => t.name === catalogEntry.name)) {
10711
+ existing.push(catalogEntry);
10712
+ this._tools.set(category, existing);
10713
+ }
10714
+ }
10715
+ }
10716
+ /**
10717
+ * Reset the registry. Primarily for testing.
10718
+ */
10719
+ static reset() {
10720
+ this._categories.clear();
10721
+ this._tools.clear();
10722
+ this._initialized = false;
10723
+ this._connectorToolsModule = null;
10724
+ }
10725
+ };
10726
+
10257
10727
  // src/core/BaseAgent.ts
10258
10728
  init_Connector();
10259
10729
 
@@ -10291,6 +10761,14 @@ var DEFAULT_ALLOWLIST = [
10291
10761
  "user_info_get",
10292
10762
  "user_info_remove",
10293
10763
  "user_info_clear",
10764
+ // TODO tools (user-specific data - safe)
10765
+ "todo_add",
10766
+ "todo_update",
10767
+ "todo_remove",
10768
+ // Tool catalog tools (browsing and loading — safe)
10769
+ "tool_catalog_search",
10770
+ "tool_catalog_load",
10771
+ "tool_catalog_unload",
10294
10772
  // Meta-tools (internal coordination)
10295
10773
  "_start_planning",
10296
10774
  "_modify_plan",
@@ -11605,6 +12083,17 @@ var ToolManager = class extends EventEmitter {
11605
12083
  if (!toolNames) return [];
11606
12084
  return Array.from(toolNames).map((name) => this.registry.get(name)).filter((reg) => reg.enabled).sort((a, b) => b.priority - a.priority).map((reg) => reg.tool);
11607
12085
  }
12086
+ /**
12087
+ * Get all registered tool names in a category.
12088
+ * Used by ToolCatalogPlugin for bulk enable/disable.
12089
+ */
12090
+ getByCategory(category) {
12091
+ const names = [];
12092
+ for (const [name, reg] of this.registry) {
12093
+ if (reg.category === category) names.push(name);
12094
+ }
12095
+ return names;
12096
+ }
11608
12097
  /**
11609
12098
  * Get tool registration info
11610
12099
  */
@@ -15770,7 +16259,35 @@ User info is automatically shown in context \u2014 no need to call user_info_get
15770
16259
 
15771
16260
  **Important:** Do not store sensitive information (passwords, tokens, PII) in user info. It is not encrypted and may be accessible to other parts of the system. Always follow best practices for security.
15772
16261
 
15773
- **Rules after each user message:** If the user provides new information about themselves, update user info accordingly. If they ask to change or remove existing information, do that as well. Always keep user info up to date with the latest information provided by the user. Learn about the user proactively!`;
16262
+ **Rules after each user message:** If the user provides new information about themselves, update user info accordingly. If they ask to change or remove existing information, do that as well. Always keep user info up to date with the latest information provided by the user. Learn about the user proactively!
16263
+
16264
+ ## TODO Management
16265
+
16266
+ TODOs are stored alongside user info and shown in a separate "Current TODOs" section in context.
16267
+
16268
+ **Tools:**
16269
+ - \`todo_add(title, description?, people?, dueDate?, tags?)\`: Create a new TODO item
16270
+ - \`todo_update(id, title?, description?, people?, dueDate?, tags?, status?)\`: Update an existing TODO
16271
+ - \`todo_remove(id)\`: Delete a TODO item
16272
+
16273
+ **Proactive creation \u2014 be helpful:**
16274
+ - If the user's message implies an action item, task, or deadline \u2192 ask "Would you like me to create a TODO for this?"
16275
+ - If the user explicitly says "remind me", "track this", "don't forget" \u2192 create a TODO immediately without asking.
16276
+ - When discussing plans with deadlines or deliverables \u2192 suggest relevant TODOs.
16277
+ - When the user mentions other people involved \u2192 include them in the \`people\` field.
16278
+ - Suggest appropriate tags based on context (e.g. "work", "personal", "urgent").
16279
+
16280
+ **Reminder rules:**
16281
+ - Check the \`_todo_last_reminded\` entry in user info. If its value is NOT today's date (YYYY-MM-DD) AND there are overdue or soon-due items (within 2 days), proactively remind the user ONCE at the start of the conversation, then set \`_todo_last_reminded\` to today's date via \`user_info_set\`.
16282
+ - Do NOT remind again in the same day unless the user explicitly asks about their TODOs.
16283
+ - When reminding, prioritize: overdue items first, then items due today, then items due tomorrow.
16284
+ - If the user asks about their TODOs or schedule, always answer regardless of reminder status.
16285
+ - After completing a TODO, mark it as done via \`todo_update(id, status: 'done')\`. Suggest marking items done when context indicates completion.
16286
+
16287
+ **Cleanup rules:**
16288
+ - Completed TODOs older than 48 hours (check updatedAt of done items) \u2192 auto-delete via \`todo_remove\` without asking.
16289
+ - Overdue pending TODOs (past due > 7 days) \u2192 ask the user: "This TODO is overdue by X days \u2014 still relevant or should I remove it?"
16290
+ - Run cleanup checks at the same time as reminders (once per day, using \`_todo_last_reminded\` marker).`;
15774
16291
  var userInfoSetDefinition = {
15775
16292
  type: "function",
15776
16293
  function: {
@@ -15847,6 +16364,102 @@ var userInfoClearDefinition = {
15847
16364
  }
15848
16365
  }
15849
16366
  };
16367
+ var todoAddDefinition = {
16368
+ type: "function",
16369
+ function: {
16370
+ name: "todo_add",
16371
+ description: "Create a new TODO item for the user. Returns the generated todo ID.",
16372
+ parameters: {
16373
+ type: "object",
16374
+ properties: {
16375
+ title: {
16376
+ type: "string",
16377
+ description: "Short title for the TODO (required)"
16378
+ },
16379
+ description: {
16380
+ type: "string",
16381
+ description: "Optional detailed description"
16382
+ },
16383
+ people: {
16384
+ type: "array",
16385
+ items: { type: "string" },
16386
+ description: "People involved besides the current user (optional)"
16387
+ },
16388
+ dueDate: {
16389
+ type: "string",
16390
+ description: "Due date in ISO format YYYY-MM-DD (optional)"
16391
+ },
16392
+ tags: {
16393
+ type: "array",
16394
+ items: { type: "string" },
16395
+ description: 'Categorization tags (optional, e.g. "work", "personal", "urgent")'
16396
+ }
16397
+ },
16398
+ required: ["title"]
16399
+ }
16400
+ }
16401
+ };
16402
+ var todoUpdateDefinition = {
16403
+ type: "function",
16404
+ function: {
16405
+ name: "todo_update",
16406
+ description: "Update an existing TODO item. Only provided fields are changed.",
16407
+ parameters: {
16408
+ type: "object",
16409
+ properties: {
16410
+ id: {
16411
+ type: "string",
16412
+ description: 'The todo ID (e.g. "todo_a1b2c3")'
16413
+ },
16414
+ title: {
16415
+ type: "string",
16416
+ description: "New title"
16417
+ },
16418
+ description: {
16419
+ type: "string",
16420
+ description: "New description (pass empty string to clear)"
16421
+ },
16422
+ people: {
16423
+ type: "array",
16424
+ items: { type: "string" },
16425
+ description: "New people list (replaces existing)"
16426
+ },
16427
+ dueDate: {
16428
+ type: "string",
16429
+ description: "New due date in ISO format YYYY-MM-DD (pass empty string to clear)"
16430
+ },
16431
+ tags: {
16432
+ type: "array",
16433
+ items: { type: "string" },
16434
+ description: "New tags list (replaces existing)"
16435
+ },
16436
+ status: {
16437
+ type: "string",
16438
+ enum: ["pending", "done"],
16439
+ description: "New status"
16440
+ }
16441
+ },
16442
+ required: ["id"]
16443
+ }
16444
+ }
16445
+ };
16446
+ var todoRemoveDefinition = {
16447
+ type: "function",
16448
+ function: {
16449
+ name: "todo_remove",
16450
+ description: "Delete a TODO item.",
16451
+ parameters: {
16452
+ type: "object",
16453
+ properties: {
16454
+ id: {
16455
+ type: "string",
16456
+ description: 'The todo ID to remove (e.g. "todo_a1b2c3")'
16457
+ }
16458
+ },
16459
+ required: ["id"]
16460
+ }
16461
+ }
16462
+ };
15850
16463
  function validateKey2(key) {
15851
16464
  if (typeof key !== "string") return "Key must be a string";
15852
16465
  const trimmed = key.trim();
@@ -15870,6 +16483,37 @@ function buildStorageContext(toolContext) {
15870
16483
  if (toolContext?.userId) return { userId: toolContext.userId };
15871
16484
  return void 0;
15872
16485
  }
16486
+ var TODO_KEY_PREFIX = "todo_";
16487
+ var INTERNAL_KEY_PREFIX = "_";
16488
+ function isTodoEntry(entry) {
16489
+ return entry.id.startsWith(TODO_KEY_PREFIX);
16490
+ }
16491
+ function isInternalEntry(entry) {
16492
+ return entry.id.startsWith(INTERNAL_KEY_PREFIX);
16493
+ }
16494
+ function generateTodoId() {
16495
+ const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
16496
+ let id = "";
16497
+ for (let i = 0; i < 6; i++) {
16498
+ id += chars[Math.floor(Math.random() * chars.length)];
16499
+ }
16500
+ return `${TODO_KEY_PREFIX}${id}`;
16501
+ }
16502
+ function renderTodoEntry(entry) {
16503
+ const val = entry.value;
16504
+ const checkbox = val.status === "done" ? "[x]" : "[ ]";
16505
+ const parts = [];
16506
+ if (val.dueDate) parts.push(`due: ${val.dueDate}`);
16507
+ if (val.people && val.people.length > 0) parts.push(`people: ${val.people.join(", ")}`);
16508
+ const meta = parts.length > 0 ? ` (${parts.join(", ")})` : "";
16509
+ const tags = val.tags && val.tags.length > 0 ? ` [${val.tags.join(", ")}]` : "";
16510
+ let line = `- ${checkbox} ${entry.id}: ${val.title}${meta}${tags}`;
16511
+ if (val.description) {
16512
+ line += `
16513
+ ${val.description}`;
16514
+ }
16515
+ return line;
16516
+ }
15873
16517
  function formatValue(value) {
15874
16518
  if (value === null) return "null";
15875
16519
  if (typeof value === "string") return value;
@@ -15937,7 +16581,10 @@ var UserInfoPluginNextGen = class {
15937
16581
  this.createUserInfoSetTool(),
15938
16582
  this.createUserInfoGetTool(),
15939
16583
  this.createUserInfoRemoveTool(),
15940
- this.createUserInfoClearTool()
16584
+ this.createUserInfoClearTool(),
16585
+ this.createTodoAddTool(),
16586
+ this.createTodoUpdateTool(),
16587
+ this.createTodoRemoveTool()
15941
16588
  ];
15942
16589
  }
15943
16590
  destroy() {
@@ -16008,9 +16655,38 @@ var UserInfoPluginNextGen = class {
16008
16655
  * Render entries as markdown for context injection
16009
16656
  */
16010
16657
  renderContent() {
16011
- const sorted = Array.from(this._entries.values()).sort((a, b) => a.createdAt - b.createdAt);
16012
- return sorted.map((entry) => `### ${entry.id}
16013
- ${formatValue(entry.value)}`).join("\n\n");
16658
+ const userEntries = [];
16659
+ const todoEntries = [];
16660
+ for (const entry of this._entries.values()) {
16661
+ if (isTodoEntry(entry)) {
16662
+ todoEntries.push(entry);
16663
+ } else if (!isInternalEntry(entry)) {
16664
+ userEntries.push(entry);
16665
+ }
16666
+ }
16667
+ const sections = [];
16668
+ if (userEntries.length > 0) {
16669
+ userEntries.sort((a, b) => a.createdAt - b.createdAt);
16670
+ sections.push(
16671
+ userEntries.map((entry) => `### ${entry.id}
16672
+ ${formatValue(entry.value)}`).join("\n\n")
16673
+ );
16674
+ }
16675
+ if (todoEntries.length > 0) {
16676
+ todoEntries.sort((a, b) => {
16677
+ const aVal = a.value;
16678
+ const bVal = b.value;
16679
+ if (aVal.status !== bVal.status) return aVal.status === "pending" ? -1 : 1;
16680
+ if (aVal.dueDate && bVal.dueDate) return aVal.dueDate.localeCompare(bVal.dueDate);
16681
+ if (aVal.dueDate) return -1;
16682
+ if (bVal.dueDate) return 1;
16683
+ return a.createdAt - b.createdAt;
16684
+ });
16685
+ sections.push(
16686
+ "## Current TODOs\n" + todoEntries.map(renderTodoEntry).join("\n")
16687
+ );
16688
+ }
16689
+ return sections.join("\n\n");
16014
16690
  }
16015
16691
  /**
16016
16692
  * Resolve storage instance (lazy singleton)
@@ -16191,6 +16867,619 @@ ${formatValue(entry.value)}`).join("\n\n");
16191
16867
  describeCall: () => "clear user info"
16192
16868
  };
16193
16869
  }
16870
+ // ============================================================================
16871
+ // TODO Tool Factories
16872
+ // ============================================================================
16873
+ createTodoAddTool() {
16874
+ return {
16875
+ definition: todoAddDefinition,
16876
+ execute: async (args, context) => {
16877
+ this.assertNotDestroyed();
16878
+ await this.ensureInitialized();
16879
+ const userId = context?.userId ?? this.userId;
16880
+ const title = args.title;
16881
+ if (!title || typeof title !== "string" || title.trim().length === 0) {
16882
+ return { error: "Title is required" };
16883
+ }
16884
+ if (this._entries.size >= this.maxEntries) {
16885
+ return { error: `Maximum number of entries reached (${this.maxEntries})` };
16886
+ }
16887
+ let todoId = generateTodoId();
16888
+ while (this._entries.has(todoId)) {
16889
+ todoId = generateTodoId();
16890
+ }
16891
+ const todoValue = {
16892
+ type: "todo",
16893
+ title: title.trim(),
16894
+ description: args.description ? String(args.description).trim() : void 0,
16895
+ people: Array.isArray(args.people) ? args.people.filter((p) => typeof p === "string") : void 0,
16896
+ dueDate: typeof args.dueDate === "string" && args.dueDate.trim() ? args.dueDate.trim() : void 0,
16897
+ tags: Array.isArray(args.tags) ? args.tags.filter((t) => typeof t === "string") : void 0,
16898
+ status: "pending"
16899
+ };
16900
+ const valueSize = calculateValueSize(todoValue);
16901
+ let currentTotal = 0;
16902
+ for (const e of this._entries.values()) {
16903
+ currentTotal += calculateValueSize(e.value);
16904
+ }
16905
+ if (currentTotal + valueSize > this.maxTotalSize) {
16906
+ return { error: `Total size would exceed maximum (${this.maxTotalSize} bytes)` };
16907
+ }
16908
+ const now = Date.now();
16909
+ const entry = {
16910
+ id: todoId,
16911
+ value: todoValue,
16912
+ valueType: "object",
16913
+ description: title.trim(),
16914
+ createdAt: now,
16915
+ updatedAt: now
16916
+ };
16917
+ this._entries.set(todoId, entry);
16918
+ this._tokenCache = null;
16919
+ await this.persistToStorage(userId);
16920
+ return {
16921
+ success: true,
16922
+ message: `TODO '${title.trim()}' created`,
16923
+ id: todoId,
16924
+ todo: todoValue
16925
+ };
16926
+ },
16927
+ permission: { scope: "always", riskLevel: "low" },
16928
+ describeCall: (args) => `add todo '${args.title}'`
16929
+ };
16930
+ }
16931
+ createTodoUpdateTool() {
16932
+ return {
16933
+ definition: todoUpdateDefinition,
16934
+ execute: async (args, context) => {
16935
+ this.assertNotDestroyed();
16936
+ await this.ensureInitialized();
16937
+ const userId = context?.userId ?? this.userId;
16938
+ const id = args.id;
16939
+ if (!id || typeof id !== "string") {
16940
+ return { error: "Todo ID is required" };
16941
+ }
16942
+ const entry = this._entries.get(id);
16943
+ if (!entry || !isTodoEntry(entry)) {
16944
+ return { error: `TODO '${id}' not found` };
16945
+ }
16946
+ const currentValue = entry.value;
16947
+ const updatedValue = { ...currentValue };
16948
+ if (typeof args.title === "string" && args.title.trim()) {
16949
+ updatedValue.title = args.title.trim();
16950
+ }
16951
+ if (args.description !== void 0) {
16952
+ updatedValue.description = typeof args.description === "string" && args.description.trim() ? args.description.trim() : void 0;
16953
+ }
16954
+ if (args.people !== void 0) {
16955
+ updatedValue.people = Array.isArray(args.people) ? args.people.filter((p) => typeof p === "string") : void 0;
16956
+ }
16957
+ if (args.dueDate !== void 0) {
16958
+ updatedValue.dueDate = typeof args.dueDate === "string" && args.dueDate.trim() ? args.dueDate.trim() : void 0;
16959
+ }
16960
+ if (args.tags !== void 0) {
16961
+ updatedValue.tags = Array.isArray(args.tags) ? args.tags.filter((t) => typeof t === "string") : void 0;
16962
+ }
16963
+ if (args.status === "pending" || args.status === "done") {
16964
+ updatedValue.status = args.status;
16965
+ }
16966
+ const valueSize = calculateValueSize(updatedValue);
16967
+ let currentTotal = 0;
16968
+ for (const e of this._entries.values()) {
16969
+ currentTotal += calculateValueSize(e.value);
16970
+ }
16971
+ const existingSize = calculateValueSize(currentValue);
16972
+ if (currentTotal - existingSize + valueSize > this.maxTotalSize) {
16973
+ return { error: `Total size would exceed maximum (${this.maxTotalSize} bytes)` };
16974
+ }
16975
+ const now = Date.now();
16976
+ const updatedEntry = {
16977
+ ...entry,
16978
+ value: updatedValue,
16979
+ description: updatedValue.title,
16980
+ updatedAt: now
16981
+ };
16982
+ this._entries.set(id, updatedEntry);
16983
+ this._tokenCache = null;
16984
+ await this.persistToStorage(userId);
16985
+ return {
16986
+ success: true,
16987
+ message: `TODO '${id}' updated`,
16988
+ id,
16989
+ todo: updatedValue
16990
+ };
16991
+ },
16992
+ permission: { scope: "always", riskLevel: "low" },
16993
+ describeCall: (args) => `update todo '${args.id}'`
16994
+ };
16995
+ }
16996
+ createTodoRemoveTool() {
16997
+ return {
16998
+ definition: todoRemoveDefinition,
16999
+ execute: async (args, context) => {
17000
+ this.assertNotDestroyed();
17001
+ await this.ensureInitialized();
17002
+ const userId = context?.userId ?? this.userId;
17003
+ const id = args.id;
17004
+ if (!id || typeof id !== "string") {
17005
+ return { error: "Todo ID is required" };
17006
+ }
17007
+ const entry = this._entries.get(id);
17008
+ if (!entry || !isTodoEntry(entry)) {
17009
+ return { error: `TODO '${id}' not found` };
17010
+ }
17011
+ this._entries.delete(id);
17012
+ this._tokenCache = null;
17013
+ await this.persistToStorage(userId);
17014
+ return {
17015
+ success: true,
17016
+ message: `TODO '${id}' removed`,
17017
+ id
17018
+ };
17019
+ },
17020
+ permission: { scope: "always", riskLevel: "low" },
17021
+ describeCall: (args) => `remove todo '${args.id}'`
17022
+ };
17023
+ }
17024
+ };
17025
+
17026
+ // src/core/context-nextgen/plugins/ToolCatalogPluginNextGen.ts
17027
+ init_Logger();
17028
+ var DEFAULT_MAX_LOADED = 10;
17029
+ var TOOL_CATALOG_INSTRUCTIONS = `## Tool Catalog
17030
+
17031
+ You have access to a dynamic tool catalog. Not all tools are loaded at once \u2014 use these metatools to discover and load what you need:
17032
+
17033
+ **tool_catalog_search** \u2014 Browse available tool categories and search for specific tools.
17034
+ - No params \u2192 list all available categories with descriptions
17035
+ - \`category\` \u2192 list tools in that category
17036
+ - \`query\` \u2192 keyword search across categories and tools
17037
+
17038
+ **tool_catalog_load** \u2014 Load a category's tools so you can use them.
17039
+ - Tools become available immediately after loading.
17040
+ - If you need tools from a category, load it first.
17041
+
17042
+ **tool_catalog_unload** \u2014 Unload a category to free token budget.
17043
+ - Unloaded tools are no longer sent to you.
17044
+ - Use when you're done with a category.
17045
+
17046
+ **Best practices:**
17047
+ - Search first to find the right category before loading.
17048
+ - Unload categories you no longer need to keep context lean.
17049
+ - Categories marked [LOADED] are already available.`;
17050
+ var catalogSearchDefinition = {
17051
+ type: "function",
17052
+ function: {
17053
+ name: "tool_catalog_search",
17054
+ description: "Search the tool catalog. No params lists categories. Use category to list tools in it, or query to keyword-search.",
17055
+ parameters: {
17056
+ type: "object",
17057
+ properties: {
17058
+ query: {
17059
+ type: "string",
17060
+ description: "Keyword to search across category names, descriptions, and tool names"
17061
+ },
17062
+ category: {
17063
+ type: "string",
17064
+ description: "Category name to list its tools"
17065
+ }
17066
+ }
17067
+ }
17068
+ }
17069
+ };
17070
+ var catalogLoadDefinition = {
17071
+ type: "function",
17072
+ function: {
17073
+ name: "tool_catalog_load",
17074
+ description: "Load all tools from a category so they become available for use.",
17075
+ parameters: {
17076
+ type: "object",
17077
+ properties: {
17078
+ category: {
17079
+ type: "string",
17080
+ description: "Category name to load"
17081
+ }
17082
+ },
17083
+ required: ["category"]
17084
+ }
17085
+ }
17086
+ };
17087
+ var catalogUnloadDefinition = {
17088
+ type: "function",
17089
+ function: {
17090
+ name: "tool_catalog_unload",
17091
+ description: "Unload a category to free token budget. Tools from this category will no longer be available.",
17092
+ parameters: {
17093
+ type: "object",
17094
+ properties: {
17095
+ category: {
17096
+ type: "string",
17097
+ description: "Category name to unload"
17098
+ }
17099
+ },
17100
+ required: ["category"]
17101
+ }
17102
+ }
17103
+ };
17104
+ var ToolCatalogPluginNextGen = class extends BasePluginNextGen {
17105
+ name = "tool_catalog";
17106
+ /** category name → array of tool names that were loaded */
17107
+ _loadedCategories = /* @__PURE__ */ new Map();
17108
+ /** Reference to the ToolManager for registering/disabling tools */
17109
+ _toolManager = null;
17110
+ /** Cached connector categories — discovered once in setToolManager() */
17111
+ _connectorCategories = null;
17112
+ /** Whether this plugin has been destroyed */
17113
+ _destroyed = false;
17114
+ /** WeakMap cache for tool definition token estimates */
17115
+ _toolTokenCache = /* @__PURE__ */ new WeakMap();
17116
+ _config;
17117
+ constructor(config) {
17118
+ super();
17119
+ this._config = {
17120
+ maxLoadedCategories: DEFAULT_MAX_LOADED,
17121
+ ...config
17122
+ };
17123
+ }
17124
+ // ========================================================================
17125
+ // Plugin Interface
17126
+ // ========================================================================
17127
+ getInstructions() {
17128
+ return TOOL_CATALOG_INSTRUCTIONS;
17129
+ }
17130
+ async getContent() {
17131
+ const categories = this.getAllowedCategories();
17132
+ if (categories.length === 0 && this.getConnectorCategories().length === 0) return null;
17133
+ const lines = ["## Tool Catalog"];
17134
+ lines.push("");
17135
+ const loaded = Array.from(this._loadedCategories.keys());
17136
+ if (loaded.length > 0) {
17137
+ lines.push(`**Loaded:** ${loaded.join(", ")}`);
17138
+ }
17139
+ lines.push(`**Available categories:** ${categories.length}`);
17140
+ for (const cat of categories) {
17141
+ const tools = ToolCatalogRegistry.getToolsInCategory(cat.name);
17142
+ const marker = this._loadedCategories.has(cat.name) ? " [LOADED]" : "";
17143
+ lines.push(`- **${cat.displayName}** (${tools.length} tools)${marker}: ${cat.description}`);
17144
+ }
17145
+ for (const cc of this.getConnectorCategories()) {
17146
+ const marker = this._loadedCategories.has(cc.name) ? " [LOADED]" : "";
17147
+ lines.push(`- **${cc.displayName}** (${cc.toolCount} tools)${marker}: ${cc.description}`);
17148
+ }
17149
+ const content = lines.join("\n");
17150
+ this.updateTokenCache(this.estimator.estimateTokens(content));
17151
+ return content;
17152
+ }
17153
+ getContents() {
17154
+ return {
17155
+ loadedCategories: Array.from(this._loadedCategories.entries()).map(([name, tools]) => ({
17156
+ category: name,
17157
+ toolCount: tools.length,
17158
+ tools
17159
+ }))
17160
+ };
17161
+ }
17162
+ getTools() {
17163
+ const plugin = this;
17164
+ const searchTool = {
17165
+ definition: catalogSearchDefinition,
17166
+ execute: async (args) => {
17167
+ return plugin.executeSearch(args.query, args.category);
17168
+ }
17169
+ };
17170
+ const loadTool = {
17171
+ definition: catalogLoadDefinition,
17172
+ execute: async (args) => {
17173
+ return plugin.executeLoad(args.category);
17174
+ }
17175
+ };
17176
+ const unloadTool = {
17177
+ definition: catalogUnloadDefinition,
17178
+ execute: async (args) => {
17179
+ return plugin.executeUnload(args.category);
17180
+ }
17181
+ };
17182
+ return [searchTool, loadTool, unloadTool];
17183
+ }
17184
+ isCompactable() {
17185
+ return this._loadedCategories.size > 0;
17186
+ }
17187
+ async compact(targetTokensToFree) {
17188
+ if (!this._toolManager || this._loadedCategories.size === 0) return 0;
17189
+ const categoriesByLastUsed = this.getCategoriesSortedByLastUsed();
17190
+ let freed = 0;
17191
+ for (const category of categoriesByLastUsed) {
17192
+ if (freed >= targetTokensToFree) break;
17193
+ const toolNames = this._loadedCategories.get(category);
17194
+ if (!toolNames) continue;
17195
+ const toolTokens = this.estimateToolDefinitionTokens(toolNames);
17196
+ this._toolManager.setEnabled(toolNames, false);
17197
+ this._loadedCategories.delete(category);
17198
+ freed += toolTokens;
17199
+ logger.debug(
17200
+ { category, toolCount: toolNames.length, freed: toolTokens },
17201
+ `[ToolCatalogPlugin] Compacted category '${category}'`
17202
+ );
17203
+ }
17204
+ this.invalidateTokenCache();
17205
+ return freed;
17206
+ }
17207
+ getState() {
17208
+ return {
17209
+ loadedCategories: Array.from(this._loadedCategories.keys())
17210
+ };
17211
+ }
17212
+ restoreState(state) {
17213
+ if (!state || typeof state !== "object") return;
17214
+ const s = state;
17215
+ if (!Array.isArray(s.loadedCategories) || s.loadedCategories.length === 0) return;
17216
+ for (const category of s.loadedCategories) {
17217
+ if (typeof category !== "string" || !category) continue;
17218
+ const result = this.executeLoad(category);
17219
+ if (result.error) {
17220
+ logger.warn(
17221
+ { category, error: result.error },
17222
+ `[ToolCatalogPlugin] Failed to restore category '${category}'`
17223
+ );
17224
+ }
17225
+ }
17226
+ this.invalidateTokenCache();
17227
+ }
17228
+ destroy() {
17229
+ this._loadedCategories.clear();
17230
+ this._toolManager = null;
17231
+ this._connectorCategories = null;
17232
+ this._destroyed = true;
17233
+ }
17234
+ // ========================================================================
17235
+ // Public API
17236
+ // ========================================================================
17237
+ /**
17238
+ * Set the ToolManager reference. Called by AgentContextNextGen after plugin registration.
17239
+ */
17240
+ setToolManager(tm) {
17241
+ this._toolManager = tm;
17242
+ this._connectorCategories = ToolCatalogRegistry.discoverConnectorCategories({
17243
+ scope: this._config.categoryScope,
17244
+ identities: this._config.identities
17245
+ });
17246
+ if (this._config.autoLoadCategories?.length) {
17247
+ for (const category of this._config.autoLoadCategories) {
17248
+ const result = this.executeLoad(category);
17249
+ if (result.error) {
17250
+ logger.warn(
17251
+ { category, error: result.error },
17252
+ `[ToolCatalogPlugin] Failed to auto-load category '${category}'`
17253
+ );
17254
+ }
17255
+ }
17256
+ }
17257
+ }
17258
+ /** Get list of currently loaded category names */
17259
+ get loadedCategories() {
17260
+ return Array.from(this._loadedCategories.keys());
17261
+ }
17262
+ // ========================================================================
17263
+ // Metatool Implementations
17264
+ // ========================================================================
17265
+ executeSearch(query, category) {
17266
+ if (this._destroyed) return { error: "Plugin destroyed" };
17267
+ if (category) {
17268
+ if (ToolCatalogRegistry.parseConnectorCategory(category) !== null) {
17269
+ return this.searchConnectorCategory(category);
17270
+ }
17271
+ if (!ToolCatalogRegistry.hasCategory(category)) {
17272
+ return { error: `Category '${category}' not found. Use tool_catalog_search with no params to see available categories.` };
17273
+ }
17274
+ if (!ToolCatalogRegistry.isCategoryAllowed(category, this._config.categoryScope)) {
17275
+ return { error: `Category '${category}' is not available for this agent.` };
17276
+ }
17277
+ const tools = ToolCatalogRegistry.getToolsInCategory(category);
17278
+ const loaded = this._loadedCategories.has(category);
17279
+ return {
17280
+ category,
17281
+ loaded,
17282
+ tools: tools.map((t) => ({
17283
+ name: t.name,
17284
+ displayName: t.displayName,
17285
+ description: t.description,
17286
+ safeByDefault: t.safeByDefault
17287
+ }))
17288
+ };
17289
+ }
17290
+ if (query) {
17291
+ return this.keywordSearch(query);
17292
+ }
17293
+ const categories = this.getAllowedCategories();
17294
+ const connectorCats = this.getConnectorCategories();
17295
+ const result = [];
17296
+ for (const cat of categories) {
17297
+ const tools = ToolCatalogRegistry.getToolsInCategory(cat.name);
17298
+ result.push({
17299
+ name: cat.name,
17300
+ displayName: cat.displayName,
17301
+ description: cat.description,
17302
+ toolCount: tools.length,
17303
+ loaded: this._loadedCategories.has(cat.name)
17304
+ });
17305
+ }
17306
+ for (const cc of connectorCats) {
17307
+ result.push({
17308
+ name: cc.name,
17309
+ displayName: cc.displayName,
17310
+ description: cc.description,
17311
+ toolCount: cc.toolCount,
17312
+ loaded: this._loadedCategories.has(cc.name)
17313
+ });
17314
+ }
17315
+ return { categories: result };
17316
+ }
17317
+ executeLoad(category) {
17318
+ if (this._destroyed) return { error: "Plugin destroyed" };
17319
+ if (!this._toolManager) {
17320
+ return { error: "ToolManager not connected. Plugin not properly initialized." };
17321
+ }
17322
+ if (!ToolCatalogRegistry.isCategoryAllowed(category, this._config.categoryScope)) {
17323
+ return { error: `Category '${category}' is not available for this agent.` };
17324
+ }
17325
+ if (this._loadedCategories.has(category)) {
17326
+ const toolNames2 = this._loadedCategories.get(category);
17327
+ return { loaded: toolNames2.length, tools: toolNames2, alreadyLoaded: true };
17328
+ }
17329
+ if (this._loadedCategories.size >= this._config.maxLoadedCategories) {
17330
+ return {
17331
+ error: `Maximum loaded categories (${this._config.maxLoadedCategories}) reached. Unload a category first.`,
17332
+ loaded: Array.from(this._loadedCategories.keys())
17333
+ };
17334
+ }
17335
+ const isConnector = ToolCatalogRegistry.parseConnectorCategory(category) !== null;
17336
+ let tools;
17337
+ if (isConnector) {
17338
+ tools = ToolCatalogRegistry.resolveConnectorCategoryTools(category);
17339
+ } else {
17340
+ const entries = ToolCatalogRegistry.getToolsInCategory(category);
17341
+ if (entries.length === 0) {
17342
+ return { error: `Category '${category}' has no tools or does not exist.` };
17343
+ }
17344
+ tools = entries.filter((e) => e.tool != null).map((e) => ({ tool: e.tool, name: e.name }));
17345
+ }
17346
+ if (tools.length === 0) {
17347
+ return { error: `No tools found for category '${category}'.` };
17348
+ }
17349
+ const toolNames = [];
17350
+ for (const { tool, name } of tools) {
17351
+ const existing = this._toolManager.getRegistration(name);
17352
+ if (existing) {
17353
+ this._toolManager.setEnabled([name], true);
17354
+ } else {
17355
+ this._toolManager.register(tool, { category, source: `catalog:${category}` });
17356
+ }
17357
+ toolNames.push(name);
17358
+ }
17359
+ this._loadedCategories.set(category, toolNames);
17360
+ this.invalidateTokenCache();
17361
+ logger.debug(
17362
+ { category, toolCount: toolNames.length, tools: toolNames },
17363
+ `[ToolCatalogPlugin] Loaded category '${category}'`
17364
+ );
17365
+ return { loaded: toolNames.length, tools: toolNames };
17366
+ }
17367
+ executeUnload(category) {
17368
+ if (this._destroyed) return { error: "Plugin destroyed" };
17369
+ if (!this._toolManager) {
17370
+ return { error: "ToolManager not connected." };
17371
+ }
17372
+ const toolNames = this._loadedCategories.get(category);
17373
+ if (!toolNames) {
17374
+ return { unloaded: 0, message: `Category '${category}' is not loaded.` };
17375
+ }
17376
+ this._toolManager.setEnabled(toolNames, false);
17377
+ this._loadedCategories.delete(category);
17378
+ this.invalidateTokenCache();
17379
+ logger.debug(
17380
+ { category, toolCount: toolNames.length },
17381
+ `[ToolCatalogPlugin] Unloaded category '${category}'`
17382
+ );
17383
+ return { unloaded: toolNames.length };
17384
+ }
17385
+ // ========================================================================
17386
+ // Helpers
17387
+ // ========================================================================
17388
+ getAllowedCategories() {
17389
+ return ToolCatalogRegistry.filterCategories(this._config.categoryScope);
17390
+ }
17391
+ /**
17392
+ * Get connector categories from cache (populated once in setToolManager).
17393
+ */
17394
+ getConnectorCategories() {
17395
+ return this._connectorCategories ?? [];
17396
+ }
17397
+ keywordSearch(query) {
17398
+ const lq = query.toLowerCase();
17399
+ const results = [];
17400
+ for (const cat of this.getAllowedCategories()) {
17401
+ const catMatch = cat.name.toLowerCase().includes(lq) || cat.displayName.toLowerCase().includes(lq) || cat.description.toLowerCase().includes(lq);
17402
+ const tools = ToolCatalogRegistry.getToolsInCategory(cat.name);
17403
+ const matchingTools = tools.filter(
17404
+ (t) => t.name.toLowerCase().includes(lq) || t.displayName.toLowerCase().includes(lq) || t.description.toLowerCase().includes(lq)
17405
+ );
17406
+ if (catMatch || matchingTools.length > 0) {
17407
+ results.push({
17408
+ category: cat.name,
17409
+ categoryDisplayName: cat.displayName,
17410
+ tools: (catMatch ? tools : matchingTools).map((t) => ({
17411
+ name: t.name,
17412
+ displayName: t.displayName,
17413
+ description: t.description
17414
+ }))
17415
+ });
17416
+ }
17417
+ }
17418
+ for (const cc of this.getConnectorCategories()) {
17419
+ if (cc.name.toLowerCase().includes(lq) || cc.displayName.toLowerCase().includes(lq) || cc.description.toLowerCase().includes(lq)) {
17420
+ results.push({
17421
+ category: cc.name,
17422
+ categoryDisplayName: cc.displayName,
17423
+ tools: cc.tools.map((t) => ({
17424
+ name: t.definition.function.name,
17425
+ displayName: t.definition.function.name.replace(/_/g, " "),
17426
+ description: t.definition.function.description || ""
17427
+ }))
17428
+ });
17429
+ }
17430
+ }
17431
+ return { query, results, totalMatches: results.length };
17432
+ }
17433
+ searchConnectorCategory(category) {
17434
+ const connectorName = ToolCatalogRegistry.parseConnectorCategory(category);
17435
+ const tools = ToolCatalogRegistry.resolveConnectorCategoryTools(category);
17436
+ const loaded = this._loadedCategories.has(category);
17437
+ return {
17438
+ category,
17439
+ loaded,
17440
+ connectorName,
17441
+ tools: tools.map((t) => ({
17442
+ name: t.name,
17443
+ description: t.tool.definition.function.description || ""
17444
+ }))
17445
+ };
17446
+ }
17447
+ getCategoriesSortedByLastUsed() {
17448
+ if (!this._toolManager) return Array.from(this._loadedCategories.keys());
17449
+ const categoryLastUsed = [];
17450
+ for (const [category, toolNames] of this._loadedCategories) {
17451
+ let maxLastUsed = 0;
17452
+ for (const name of toolNames) {
17453
+ const reg = this._toolManager.getRegistration(name);
17454
+ if (reg?.metadata?.lastUsed) {
17455
+ const ts = reg.metadata.lastUsed instanceof Date ? reg.metadata.lastUsed.getTime() : 0;
17456
+ if (ts > maxLastUsed) maxLastUsed = ts;
17457
+ }
17458
+ }
17459
+ categoryLastUsed.push({ category, lastUsed: maxLastUsed });
17460
+ }
17461
+ categoryLastUsed.sort((a, b) => a.lastUsed - b.lastUsed);
17462
+ return categoryLastUsed.map((c) => c.category);
17463
+ }
17464
+ estimateToolDefinitionTokens(toolNames) {
17465
+ let total = 0;
17466
+ for (const name of toolNames) {
17467
+ const reg = this._toolManager?.getRegistration(name);
17468
+ if (reg) {
17469
+ const defObj = reg.tool.definition;
17470
+ const cached = this._toolTokenCache.get(defObj);
17471
+ if (cached !== void 0) {
17472
+ total += cached;
17473
+ } else {
17474
+ const defStr = JSON.stringify(defObj);
17475
+ const tokens = this.estimator.estimateTokens(defStr);
17476
+ this._toolTokenCache.set(defObj, tokens);
17477
+ total += tokens;
17478
+ }
17479
+ }
17480
+ }
17481
+ return total;
17482
+ }
16194
17483
  };
16195
17484
 
16196
17485
  // src/core/context-nextgen/AgentContextNextGen.ts
@@ -16846,7 +18135,8 @@ var DEFAULT_FEATURES = {
16846
18135
  workingMemory: true,
16847
18136
  inContextMemory: true,
16848
18137
  persistentInstructions: false,
16849
- userInfo: false
18138
+ userInfo: false,
18139
+ toolCatalog: false
16850
18140
  };
16851
18141
  var DEFAULT_CONFIG2 = {
16852
18142
  responseReserve: 4096,
@@ -16918,6 +18208,7 @@ var AgentContextNextGen = class _AgentContextNextGen extends EventEmitter {
16918
18208
  strategy: config.strategy ?? DEFAULT_CONFIG2.strategy,
16919
18209
  features: { ...DEFAULT_FEATURES, ...config.features },
16920
18210
  agentId: config.agentId ?? this.generateId(),
18211
+ toolCategories: config.toolCategories,
16921
18212
  storage: config.storage
16922
18213
  };
16923
18214
  this._systemPrompt = config.systemPrompt;
@@ -16973,6 +18264,16 @@ var AgentContextNextGen = class _AgentContextNextGen extends EventEmitter {
16973
18264
  ...uiConfig
16974
18265
  }));
16975
18266
  }
18267
+ if (features.toolCatalog) {
18268
+ const tcConfig = configs.toolCatalog;
18269
+ const plugin = new ToolCatalogPluginNextGen({
18270
+ categoryScope: this._config.toolCategories,
18271
+ identities: this._identities,
18272
+ ...tcConfig
18273
+ });
18274
+ this.registerPlugin(plugin);
18275
+ plugin.setToolManager(this._tools);
18276
+ }
16976
18277
  this.validateStrategyDependencies(this._compactionStrategy);
16977
18278
  }
16978
18279
  /**
@@ -24694,7 +25995,9 @@ var ROUTINE_KEYS = {
24694
25995
  /** Running fold accumulator (ICM) */
24695
25996
  FOLD_ACCUMULATOR: "__fold_accumulator",
24696
25997
  /** Prefix for large dep results stored in WM findings tier */
24697
- WM_DEP_FINDINGS_PREFIX: "findings/__dep_result_"
25998
+ WM_DEP_FINDINGS_PREFIX: "findings/__dep_result_",
25999
+ /** Prefix for auto-stored task outputs (set by output contracts) */
26000
+ TASK_OUTPUT_PREFIX: "__task_output_"
24698
26001
  };
24699
26002
  function resolveTemplates(text, inputs, icmPlugin) {
24700
26003
  return text.replace(/\{\{(\w+)\.(\w+)\}\}/g, (_match, namespace, key) => {
@@ -24719,13 +26022,45 @@ function resolveTemplates(text, inputs, icmPlugin) {
24719
26022
  function resolveTaskTemplates(task, inputs, icmPlugin) {
24720
26023
  const resolvedDescription = resolveTemplates(task.description, inputs, icmPlugin);
24721
26024
  const resolvedExpectedOutput = task.expectedOutput ? resolveTemplates(task.expectedOutput, inputs, icmPlugin) : task.expectedOutput;
24722
- if (resolvedDescription === task.description && resolvedExpectedOutput === task.expectedOutput) {
26025
+ let resolvedControlFlow = task.controlFlow;
26026
+ if (task.controlFlow && "source" in task.controlFlow) {
26027
+ const flow = task.controlFlow;
26028
+ const source = flow.source;
26029
+ if (typeof source === "string") {
26030
+ const r = resolveTemplates(source, inputs, icmPlugin);
26031
+ if (r !== source) {
26032
+ resolvedControlFlow = { ...flow, source: r };
26033
+ }
26034
+ } else if (source) {
26035
+ let changed = false;
26036
+ const resolved = { ...source };
26037
+ if (resolved.task) {
26038
+ const r = resolveTemplates(resolved.task, inputs, icmPlugin);
26039
+ if (r !== resolved.task) {
26040
+ resolved.task = r;
26041
+ changed = true;
26042
+ }
26043
+ }
26044
+ if (resolved.key) {
26045
+ const r = resolveTemplates(resolved.key, inputs, icmPlugin);
26046
+ if (r !== resolved.key) {
26047
+ resolved.key = r;
26048
+ changed = true;
26049
+ }
26050
+ }
26051
+ if (changed) {
26052
+ resolvedControlFlow = { ...flow, source: resolved };
26053
+ }
26054
+ }
26055
+ }
26056
+ if (resolvedDescription === task.description && resolvedExpectedOutput === task.expectedOutput && resolvedControlFlow === task.controlFlow) {
24723
26057
  return task;
24724
26058
  }
24725
26059
  return {
24726
26060
  ...task,
24727
26061
  description: resolvedDescription,
24728
- expectedOutput: resolvedExpectedOutput
26062
+ expectedOutput: resolvedExpectedOutput,
26063
+ controlFlow: resolvedControlFlow
24729
26064
  };
24730
26065
  }
24731
26066
  function validateAndResolveInputs(parameters, inputs) {
@@ -24793,17 +26128,6 @@ function cleanFoldKeys(icmPlugin) {
24793
26128
  cleanMapKeys(icmPlugin);
24794
26129
  icmPlugin.delete(ROUTINE_KEYS.FOLD_ACCUMULATOR);
24795
26130
  }
24796
- async function readSourceArray(flow, flowType, icmPlugin, wmPlugin) {
24797
- const sourceValue = await readMemoryValue(flow.sourceKey, icmPlugin, wmPlugin);
24798
- if (!Array.isArray(sourceValue)) {
24799
- return {
24800
- completed: false,
24801
- error: `${flowType} sourceKey "${flow.sourceKey}" is not an array (got ${typeof sourceValue})`
24802
- };
24803
- }
24804
- const maxIter = Math.min(sourceValue.length, flow.maxIterations ?? sourceValue.length, HARD_MAX_ITERATIONS);
24805
- return { array: sourceValue, maxIter };
24806
- }
24807
26131
  function prepareSubRoutine(tasks, parentTaskName) {
24808
26132
  const subRoutine = resolveSubRoutine(tasks, parentTaskName);
24809
26133
  return {
@@ -24838,10 +26162,141 @@ async function withTimeout(promise, timeoutMs, label) {
24838
26162
  clearTimeout(timer);
24839
26163
  }
24840
26164
  }
24841
- async function handleMap(agent, flow, task, inputs) {
26165
+ var COMMON_ARRAY_FIELDS = ["data", "items", "results", "entries", "list", "records", "values", "elements"];
26166
+ function extractByPath(value, path6) {
26167
+ let current = value;
26168
+ if (typeof current === "string") {
26169
+ try {
26170
+ current = JSON.parse(current);
26171
+ } catch {
26172
+ return void 0;
26173
+ }
26174
+ }
26175
+ const segments = path6.replace(/\[(\d+)\]/g, ".$1").split(".").filter(Boolean);
26176
+ for (const segment of segments) {
26177
+ if (current == null || typeof current !== "object") return void 0;
26178
+ current = current[segment];
26179
+ }
26180
+ return current;
26181
+ }
26182
+ function tryCoerceToArray(value) {
26183
+ if (value === void 0 || value === null) return value;
26184
+ if (Array.isArray(value)) return value;
26185
+ if (typeof value === "string") {
26186
+ try {
26187
+ const parsed = JSON.parse(value);
26188
+ if (Array.isArray(parsed)) return parsed;
26189
+ if (typeof parsed === "object" && parsed !== null) {
26190
+ value = parsed;
26191
+ } else {
26192
+ return value;
26193
+ }
26194
+ } catch {
26195
+ return value;
26196
+ }
26197
+ }
26198
+ if (typeof value === "object" && value !== null) {
26199
+ for (const field of COMMON_ARRAY_FIELDS) {
26200
+ const candidate = value[field];
26201
+ if (Array.isArray(candidate)) return candidate;
26202
+ }
26203
+ }
26204
+ return value;
26205
+ }
26206
+ async function llmExtractArray(agent, rawValue) {
26207
+ const serialized = typeof rawValue === "string" ? rawValue : JSON.stringify(rawValue);
26208
+ const truncated = serialized.length > 8e3 ? serialized.slice(0, 8e3) + "\n...(truncated)" : serialized;
26209
+ const response = await agent.runDirect(
26210
+ [
26211
+ "Extract a JSON array from the data below for iteration.",
26212
+ "Return ONLY a valid JSON array. No explanation, no markdown fences, no extra text.",
26213
+ "If the data contains a list in any format (JSON, markdown, numbered list, comma-separated), convert it to a JSON array of items.",
26214
+ "",
26215
+ "Data:",
26216
+ truncated
26217
+ ].join("\n"),
26218
+ { temperature: 0, maxOutputTokens: 4096 }
26219
+ );
26220
+ const text = response.output_text?.trim() ?? "";
26221
+ const cleaned = text.replace(/^```(?:json|JSON)?\s*\n?/, "").replace(/\n?\s*```$/, "").trim();
26222
+ let parsed;
26223
+ try {
26224
+ parsed = JSON.parse(cleaned);
26225
+ } catch (parseErr) {
26226
+ throw new Error(`LLM returned invalid JSON: ${parseErr.message}. Raw: "${text.slice(0, 200)}"`);
26227
+ }
26228
+ if (!Array.isArray(parsed)) {
26229
+ throw new Error(`LLM extraction produced ${typeof parsed}, expected array`);
26230
+ }
26231
+ return parsed;
26232
+ }
26233
+ async function resolveFlowSource(flow, flowType, agent, execution, icmPlugin, wmPlugin) {
26234
+ const log = logger.child({ fn: "resolveFlowSource", flowType });
26235
+ const source = flow.source;
26236
+ const lookups = [];
26237
+ if (typeof source === "string") {
26238
+ lookups.push({ key: source, label: `key "${source}"` });
26239
+ } else if (source.task) {
26240
+ lookups.push({
26241
+ key: `${ROUTINE_KEYS.TASK_OUTPUT_PREFIX}${source.task}`,
26242
+ path: source.path,
26243
+ label: `task output "${source.task}"`
26244
+ });
26245
+ if (execution) {
26246
+ const depTask = execution.plan.tasks.find((t) => t.name === source.task);
26247
+ if (depTask) {
26248
+ lookups.push({
26249
+ key: `${ROUTINE_KEYS.DEP_RESULT_PREFIX}${depTask.id}`,
26250
+ path: source.path,
26251
+ label: `dep result "${source.task}"`
26252
+ });
26253
+ }
26254
+ }
26255
+ } else if (source.key) {
26256
+ lookups.push({ key: source.key, path: source.path, label: `key "${source.key}"` });
26257
+ }
26258
+ if (lookups.length === 0) {
26259
+ return { completed: false, error: `${flowType}: source has no task, key, or string value` };
26260
+ }
26261
+ let rawValue;
26262
+ let resolvedVia;
26263
+ for (const { key, path: path6, label } of lookups) {
26264
+ const value2 = await readMemoryValue(key, icmPlugin, wmPlugin);
26265
+ if (value2 !== void 0) {
26266
+ rawValue = path6 ? extractByPath(value2, path6) : value2;
26267
+ resolvedVia = label;
26268
+ break;
26269
+ }
26270
+ }
26271
+ if (rawValue === void 0) {
26272
+ const tried = lookups.map((l) => l.label).join(", ");
26273
+ return { completed: false, error: `${flowType}: source not found (tried: ${tried})` };
26274
+ }
26275
+ log.debug({ resolvedVia }, "Source value found");
26276
+ let value = tryCoerceToArray(rawValue);
26277
+ if (!Array.isArray(value)) {
26278
+ log.info({ resolvedVia, valueType: typeof value }, "Source not an array, attempting LLM extraction");
26279
+ try {
26280
+ value = await llmExtractArray(agent, rawValue);
26281
+ log.info({ extractedLength: value.length }, "LLM extraction succeeded");
26282
+ } catch (err) {
26283
+ return {
26284
+ completed: false,
26285
+ error: `${flowType}: source value is not an array and LLM extraction failed: ${err.message}`
26286
+ };
26287
+ }
26288
+ }
26289
+ const arr = value;
26290
+ if (arr.length === 0) {
26291
+ log.warn("Source array is empty");
26292
+ }
26293
+ const maxIter = Math.min(arr.length, flow.maxIterations ?? arr.length, HARD_MAX_ITERATIONS);
26294
+ return { array: arr, maxIter };
26295
+ }
26296
+ async function handleMap(agent, flow, task, inputs, execution) {
24842
26297
  const { icmPlugin, wmPlugin } = getPlugins(agent);
24843
26298
  const log = logger.child({ controlFlow: "map", task: task.name });
24844
- const sourceResult = await readSourceArray(flow, "Map", icmPlugin, wmPlugin);
26299
+ const sourceResult = await resolveFlowSource(flow, "Map", agent, execution, icmPlugin, wmPlugin);
24845
26300
  if ("completed" in sourceResult) return sourceResult;
24846
26301
  const { array: array3, maxIter } = sourceResult;
24847
26302
  const results = [];
@@ -24879,10 +26334,10 @@ async function handleMap(agent, flow, task, inputs) {
24879
26334
  log.info({ resultCount: results.length }, "Map completed");
24880
26335
  return { completed: true, result: results };
24881
26336
  }
24882
- async function handleFold(agent, flow, task, inputs) {
26337
+ async function handleFold(agent, flow, task, inputs, execution) {
24883
26338
  const { icmPlugin, wmPlugin } = getPlugins(agent);
24884
26339
  const log = logger.child({ controlFlow: "fold", task: task.name });
24885
- const sourceResult = await readSourceArray(flow, "Fold", icmPlugin, wmPlugin);
26340
+ const sourceResult = await resolveFlowSource(flow, "Fold", agent, execution, icmPlugin, wmPlugin);
24886
26341
  if ("completed" in sourceResult) return sourceResult;
24887
26342
  const { array: array3, maxIter } = sourceResult;
24888
26343
  let accumulator = flow.initialValue;
@@ -24973,13 +26428,13 @@ async function handleUntil(agent, flow, task, inputs) {
24973
26428
  }
24974
26429
  return { completed: false, error: `Until loop: maxIterations (${flow.maxIterations}) exceeded` };
24975
26430
  }
24976
- async function executeControlFlow(agent, task, inputs) {
26431
+ async function executeControlFlow(agent, task, inputs, execution) {
24977
26432
  const flow = task.controlFlow;
24978
26433
  switch (flow.type) {
24979
26434
  case "map":
24980
- return handleMap(agent, flow, task, inputs);
26435
+ return handleMap(agent, flow, task, inputs, execution);
24981
26436
  case "fold":
24982
- return handleFold(agent, flow, task, inputs);
26437
+ return handleFold(agent, flow, task, inputs, execution);
24983
26438
  case "until":
24984
26439
  return handleUntil(agent, flow, task, inputs);
24985
26440
  default:
@@ -25008,7 +26463,26 @@ function defaultSystemPrompt(definition) {
25008
26463
  );
25009
26464
  return parts.join("\n");
25010
26465
  }
25011
- function defaultTaskPrompt(task) {
26466
+ function getOutputContracts(execution, currentTask) {
26467
+ const contracts = [];
26468
+ for (const task of execution.plan.tasks) {
26469
+ if (task.status !== "pending" || !task.controlFlow) continue;
26470
+ const flow = task.controlFlow;
26471
+ if (flow.type === "until") continue;
26472
+ const source = flow.source;
26473
+ const sourceTaskName = typeof source === "string" ? void 0 : source.task;
26474
+ if (sourceTaskName === currentTask.name) {
26475
+ contracts.push({
26476
+ storageKey: `${ROUTINE_KEYS.TASK_OUTPUT_PREFIX}${currentTask.name}`,
26477
+ format: "array",
26478
+ consumingTaskName: task.name,
26479
+ flowType: flow.type
26480
+ });
26481
+ }
26482
+ }
26483
+ return contracts;
26484
+ }
26485
+ function defaultTaskPrompt(task, execution) {
25012
26486
  const parts = [];
25013
26487
  parts.push(`## Current Task: ${task.name}`, "");
25014
26488
  parts.push(task.description, "");
@@ -25033,6 +26507,21 @@ function defaultTaskPrompt(task) {
25033
26507
  parts.push("Review the plan overview and dependency results before starting.");
25034
26508
  parts.push("");
25035
26509
  }
26510
+ if (execution) {
26511
+ const contracts = getOutputContracts(execution, task);
26512
+ if (contracts.length > 0) {
26513
+ parts.push("### Output Storage");
26514
+ for (const contract of contracts) {
26515
+ parts.push(
26516
+ `A downstream task ("${contract.consumingTaskName}") will ${contract.flowType} over your results.`,
26517
+ `Store your result as a JSON ${contract.format} using:`,
26518
+ ` context_set("${contract.storageKey}", <your JSON ${contract.format}>)`,
26519
+ "Each array element should represent one item to process independently.",
26520
+ ""
26521
+ );
26522
+ }
26523
+ }
26524
+ }
25036
26525
  parts.push("After completing the work, store key results in memory once, then respond with a text summary (no more tool calls).");
25037
26526
  return parts.join("\n");
25038
26527
  }
@@ -25189,8 +26678,8 @@ var DEP_CLEANUP_CONFIG = {
25189
26678
  wmPrefixes: [ROUTINE_KEYS.DEP_RESULT_PREFIX, ROUTINE_KEYS.WM_DEP_FINDINGS_PREFIX]
25190
26679
  };
25191
26680
  var FULL_CLEANUP_CONFIG = {
25192
- icmPrefixes: ["__routine_", ROUTINE_KEYS.DEP_RESULT_PREFIX, "__map_", "__fold_"],
25193
- wmPrefixes: [ROUTINE_KEYS.DEP_RESULT_PREFIX, ROUTINE_KEYS.WM_DEP_FINDINGS_PREFIX]
26681
+ icmPrefixes: ["__routine_", ROUTINE_KEYS.DEP_RESULT_PREFIX, "__map_", "__fold_", ROUTINE_KEYS.TASK_OUTPUT_PREFIX],
26682
+ wmPrefixes: [ROUTINE_KEYS.DEP_RESULT_PREFIX, ROUTINE_KEYS.WM_DEP_FINDINGS_PREFIX, ROUTINE_KEYS.TASK_OUTPUT_PREFIX]
25194
26683
  };
25195
26684
  async function injectRoutineContext(agent, execution, definition, currentTask) {
25196
26685
  const { icmPlugin, wmPlugin } = getPlugins(agent);
@@ -25301,7 +26790,8 @@ async function executeRoutine(options) {
25301
26790
  execution.startedAt = Date.now();
25302
26791
  execution.lastUpdatedAt = Date.now();
25303
26792
  const buildSystemPrompt = prompts?.system ?? defaultSystemPrompt;
25304
- const buildTaskPrompt = prompts?.task ?? defaultTaskPrompt;
26793
+ const userTaskPromptBuilder = prompts?.task ?? defaultTaskPrompt;
26794
+ const buildTaskPrompt = (task) => userTaskPromptBuilder(task, execution);
25305
26795
  const buildValidationPrompt = prompts?.validation ?? defaultValidationPrompt;
25306
26796
  let agent;
25307
26797
  const registeredHooks = [];
@@ -25392,7 +26882,7 @@ async function executeRoutine(options) {
25392
26882
  const { icmPlugin } = getPlugins(agent);
25393
26883
  if (getTask().controlFlow) {
25394
26884
  try {
25395
- const cfResult = await executeControlFlow(agent, getTask(), resolvedInputs);
26885
+ const cfResult = await executeControlFlow(agent, getTask(), resolvedInputs, execution);
25396
26886
  if (cfResult.completed) {
25397
26887
  execution.plan.tasks[taskIndex] = updateTaskStatus(getTask(), "completed");
25398
26888
  execution.plan.tasks[taskIndex].result = { success: true, output: cfResult.result };
@@ -25542,6 +27032,261 @@ async function executeRoutine(options) {
25542
27032
  }
25543
27033
  }
25544
27034
 
27035
+ // src/core/createExecutionRecorder.ts
27036
+ init_Logger();
27037
+ function truncate(value, maxLen) {
27038
+ const str = typeof value === "string" ? value : JSON.stringify(value);
27039
+ if (!str) return "";
27040
+ return str.length > maxLen ? str.slice(0, maxLen) + "...(truncated)" : str;
27041
+ }
27042
+ function safeCall(fn, prefix) {
27043
+ try {
27044
+ const result = fn();
27045
+ if (result && typeof result.catch === "function") {
27046
+ result.catch((err) => {
27047
+ logger.debug({ error: err.message }, `${prefix} async error`);
27048
+ });
27049
+ }
27050
+ } catch (err) {
27051
+ logger.debug({ error: err.message }, `${prefix} sync error`);
27052
+ }
27053
+ }
27054
+ function createExecutionRecorder(options) {
27055
+ const { storage, executionId, logPrefix = "[Recorder]", maxTruncateLength = 500 } = options;
27056
+ const log = logger.child({ executionId });
27057
+ let currentTaskName = "(unknown)";
27058
+ function pushStep(step) {
27059
+ safeCall(() => storage.pushStep(executionId, step), `${logPrefix} pushStep`);
27060
+ }
27061
+ function heartbeat() {
27062
+ safeCall(
27063
+ () => storage.update(executionId, { lastActivityAt: Date.now() }),
27064
+ `${logPrefix} heartbeat`
27065
+ );
27066
+ }
27067
+ const hooks = {
27068
+ "before:llm": (ctx) => {
27069
+ pushStep({
27070
+ timestamp: Date.now(),
27071
+ taskName: currentTaskName,
27072
+ type: "llm.start",
27073
+ data: { iteration: ctx.iteration }
27074
+ });
27075
+ return {};
27076
+ },
27077
+ "after:llm": (ctx) => {
27078
+ pushStep({
27079
+ timestamp: Date.now(),
27080
+ taskName: currentTaskName,
27081
+ type: "llm.complete",
27082
+ data: {
27083
+ iteration: ctx.iteration,
27084
+ duration: ctx.duration,
27085
+ tokens: ctx.response?.usage
27086
+ }
27087
+ });
27088
+ return {};
27089
+ },
27090
+ "before:tool": (ctx) => {
27091
+ pushStep({
27092
+ timestamp: Date.now(),
27093
+ taskName: currentTaskName,
27094
+ type: "tool.start",
27095
+ data: {
27096
+ toolName: ctx.toolCall.function.name,
27097
+ args: truncate(ctx.toolCall.function.arguments, maxTruncateLength)
27098
+ }
27099
+ });
27100
+ return {};
27101
+ },
27102
+ "after:tool": (ctx) => {
27103
+ pushStep({
27104
+ timestamp: Date.now(),
27105
+ taskName: currentTaskName,
27106
+ type: "tool.call",
27107
+ data: {
27108
+ toolName: ctx.toolCall.function.name,
27109
+ args: truncate(ctx.toolCall.function.arguments, maxTruncateLength),
27110
+ result: truncate(ctx.result?.content, maxTruncateLength),
27111
+ error: ctx.result?.error ? true : void 0
27112
+ }
27113
+ });
27114
+ return {};
27115
+ },
27116
+ "after:execution": () => {
27117
+ pushStep({
27118
+ timestamp: Date.now(),
27119
+ taskName: currentTaskName,
27120
+ type: "iteration.complete"
27121
+ });
27122
+ },
27123
+ "pause:check": () => {
27124
+ heartbeat();
27125
+ return { shouldPause: false };
27126
+ }
27127
+ };
27128
+ const onTaskStarted = (task, execution) => {
27129
+ currentTaskName = task.name;
27130
+ const now = Date.now();
27131
+ safeCall(
27132
+ () => storage.updateTask(executionId, task.name, {
27133
+ status: "in_progress",
27134
+ startedAt: now,
27135
+ attempts: task.attempts
27136
+ }),
27137
+ `${logPrefix} onTaskStarted`
27138
+ );
27139
+ pushStep({
27140
+ timestamp: now,
27141
+ taskName: task.name,
27142
+ type: "task.started",
27143
+ data: { taskId: task.id }
27144
+ });
27145
+ if (task.controlFlow) {
27146
+ pushStep({
27147
+ timestamp: now,
27148
+ taskName: task.name,
27149
+ type: "control_flow.started",
27150
+ data: { flowType: task.controlFlow.type }
27151
+ });
27152
+ }
27153
+ safeCall(
27154
+ () => storage.update(executionId, {
27155
+ progress: execution.progress,
27156
+ lastActivityAt: now
27157
+ }),
27158
+ `${logPrefix} onTaskStarted progress`
27159
+ );
27160
+ };
27161
+ const onTaskComplete = (task, execution) => {
27162
+ const now = Date.now();
27163
+ safeCall(
27164
+ () => storage.updateTask(executionId, task.name, {
27165
+ status: "completed",
27166
+ completedAt: now,
27167
+ attempts: task.attempts,
27168
+ result: task.result ? {
27169
+ success: true,
27170
+ output: truncate(task.result.output, maxTruncateLength),
27171
+ validationScore: task.result.validationScore,
27172
+ validationExplanation: task.result.validationExplanation
27173
+ } : void 0
27174
+ }),
27175
+ `${logPrefix} onTaskComplete`
27176
+ );
27177
+ pushStep({
27178
+ timestamp: now,
27179
+ taskName: task.name,
27180
+ type: "task.completed",
27181
+ data: {
27182
+ taskId: task.id,
27183
+ validationScore: task.result?.validationScore
27184
+ }
27185
+ });
27186
+ if (task.controlFlow) {
27187
+ pushStep({
27188
+ timestamp: now,
27189
+ taskName: task.name,
27190
+ type: "control_flow.completed",
27191
+ data: { flowType: task.controlFlow.type }
27192
+ });
27193
+ }
27194
+ safeCall(
27195
+ () => storage.update(executionId, {
27196
+ progress: execution.progress,
27197
+ lastActivityAt: now
27198
+ }),
27199
+ `${logPrefix} onTaskComplete progress`
27200
+ );
27201
+ };
27202
+ const onTaskFailed = (task, execution) => {
27203
+ const now = Date.now();
27204
+ safeCall(
27205
+ () => storage.updateTask(executionId, task.name, {
27206
+ status: "failed",
27207
+ completedAt: now,
27208
+ attempts: task.attempts,
27209
+ result: task.result ? {
27210
+ success: false,
27211
+ error: task.result.error,
27212
+ validationScore: task.result.validationScore,
27213
+ validationExplanation: task.result.validationExplanation
27214
+ } : void 0
27215
+ }),
27216
+ `${logPrefix} onTaskFailed`
27217
+ );
27218
+ pushStep({
27219
+ timestamp: now,
27220
+ taskName: task.name,
27221
+ type: "task.failed",
27222
+ data: {
27223
+ taskId: task.id,
27224
+ error: task.result?.error,
27225
+ attempts: task.attempts
27226
+ }
27227
+ });
27228
+ safeCall(
27229
+ () => storage.update(executionId, {
27230
+ progress: execution.progress,
27231
+ lastActivityAt: now
27232
+ }),
27233
+ `${logPrefix} onTaskFailed progress`
27234
+ );
27235
+ };
27236
+ const onTaskValidation = (task, result, _execution) => {
27237
+ pushStep({
27238
+ timestamp: Date.now(),
27239
+ taskName: task.name,
27240
+ type: "task.validation",
27241
+ data: {
27242
+ taskId: task.id,
27243
+ isComplete: result.isComplete,
27244
+ completionScore: result.completionScore,
27245
+ explanation: truncate(result.explanation, maxTruncateLength)
27246
+ }
27247
+ });
27248
+ };
27249
+ const finalize = async (execution, error) => {
27250
+ const now = Date.now();
27251
+ try {
27252
+ if (error || !execution) {
27253
+ await storage.update(executionId, {
27254
+ status: "failed",
27255
+ error: error?.message ?? "Unknown error",
27256
+ completedAt: now,
27257
+ lastActivityAt: now
27258
+ });
27259
+ if (error) {
27260
+ await storage.pushStep(executionId, {
27261
+ timestamp: now,
27262
+ taskName: currentTaskName,
27263
+ type: "execution.error",
27264
+ data: { error: error.message }
27265
+ });
27266
+ }
27267
+ } else {
27268
+ await storage.update(executionId, {
27269
+ status: execution.status,
27270
+ progress: execution.progress,
27271
+ error: execution.error,
27272
+ completedAt: execution.completedAt ?? now,
27273
+ lastActivityAt: now
27274
+ });
27275
+ }
27276
+ } catch (err) {
27277
+ log.error({ error: err.message }, `${logPrefix} finalize error`);
27278
+ }
27279
+ };
27280
+ return {
27281
+ hooks,
27282
+ onTaskStarted,
27283
+ onTaskComplete,
27284
+ onTaskFailed,
27285
+ onTaskValidation,
27286
+ finalize
27287
+ };
27288
+ }
27289
+
25545
27290
  // src/core/index.ts
25546
27291
  init_constants();
25547
27292
  (class {
@@ -38866,6 +40611,38 @@ var InMemoryHistoryStorage = class {
38866
40611
  this.summaries = state.summaries ? [...state.summaries] : [];
38867
40612
  }
38868
40613
  };
40614
+
40615
+ // src/domain/entities/RoutineExecutionRecord.ts
40616
+ function createTaskSnapshots(definition) {
40617
+ return definition.tasks.map((task) => ({
40618
+ taskId: task.id ?? task.name,
40619
+ name: task.name,
40620
+ description: task.description,
40621
+ status: "pending",
40622
+ attempts: 0,
40623
+ maxAttempts: task.maxAttempts ?? 3,
40624
+ controlFlowType: task.controlFlow?.type
40625
+ }));
40626
+ }
40627
+ function createRoutineExecutionRecord(definition, connectorName, model, trigger) {
40628
+ const now = Date.now();
40629
+ const executionId = `rexec-${now}-${Math.random().toString(36).substr(2, 9)}`;
40630
+ return {
40631
+ executionId,
40632
+ routineId: definition.id,
40633
+ routineName: definition.name,
40634
+ status: "running",
40635
+ progress: 0,
40636
+ tasks: createTaskSnapshots(definition),
40637
+ steps: [],
40638
+ taskCount: definition.tasks.length,
40639
+ connectorName,
40640
+ model,
40641
+ startedAt: now,
40642
+ lastActivityAt: now,
40643
+ trigger: trigger ?? { type: "manual" }
40644
+ };
40645
+ }
38869
40646
  function getDefaultBaseDirectory3() {
38870
40647
  const platform2 = process.platform;
38871
40648
  if (platform2 === "win32") {
@@ -44709,6 +46486,7 @@ __export(tools_exports, {
44709
46486
  createEditMeetingTool: () => createEditMeetingTool,
44710
46487
  createExecuteJavaScriptTool: () => createExecuteJavaScriptTool,
44711
46488
  createFindMeetingSlotsTool: () => createFindMeetingSlotsTool,
46489
+ createGenerateRoutine: () => createGenerateRoutine,
44712
46490
  createGetMeetingTranscriptTool: () => createGetMeetingTranscriptTool,
44713
46491
  createGetPRTool: () => createGetPRTool,
44714
46492
  createGitHubReadFileTool: () => createGitHubReadFileTool,
@@ -44717,6 +46495,9 @@ __export(tools_exports, {
44717
46495
  createImageGenerationTool: () => createImageGenerationTool,
44718
46496
  createListDirectoryTool: () => createListDirectoryTool,
44719
46497
  createMeetingTool: () => createMeetingTool,
46498
+ createMicrosoftListFilesTool: () => createMicrosoftListFilesTool,
46499
+ createMicrosoftReadFileTool: () => createMicrosoftReadFileTool,
46500
+ createMicrosoftSearchFilesTool: () => createMicrosoftSearchFilesTool,
44720
46501
  createPRCommentsTool: () => createPRCommentsTool,
44721
46502
  createPRFilesTool: () => createPRFilesTool,
44722
46503
  createReadFileTool: () => createReadFileTool,
@@ -44749,14 +46530,18 @@ __export(tools_exports, {
44749
46530
  desktopWindowList: () => desktopWindowList,
44750
46531
  developerTools: () => developerTools,
44751
46532
  editFile: () => editFile,
46533
+ encodeSharingUrl: () => encodeSharingUrl,
44752
46534
  executeInVM: () => executeInVM,
44753
46535
  executeJavaScript: () => executeJavaScript,
44754
46536
  expandTilde: () => expandTilde,
44755
46537
  formatAttendees: () => formatAttendees,
46538
+ formatFileSize: () => formatFileSize,
44756
46539
  formatRecipients: () => formatRecipients,
46540
+ generateRoutine: () => generateRoutine,
44757
46541
  getAllBuiltInTools: () => getAllBuiltInTools,
44758
46542
  getBackgroundOutput: () => getBackgroundOutput,
44759
46543
  getDesktopDriver: () => getDesktopDriver,
46544
+ getDrivePrefix: () => getDrivePrefix,
44760
46545
  getMediaOutputHandler: () => getMediaOutputHandler,
44761
46546
  getMediaStorage: () => getMediaStorage,
44762
46547
  getToolByName: () => getToolByName,
@@ -44770,7 +46555,9 @@ __export(tools_exports, {
44770
46555
  hydrateCustomTool: () => hydrateCustomTool,
44771
46556
  isBlockedCommand: () => isBlockedCommand,
44772
46557
  isExcludedExtension: () => isExcludedExtension,
46558
+ isMicrosoftFileUrl: () => isMicrosoftFileUrl,
44773
46559
  isTeamsMeetingUrl: () => isTeamsMeetingUrl,
46560
+ isWebUrl: () => isWebUrl,
44774
46561
  jsonManipulator: () => jsonManipulator,
44775
46562
  killBackgroundProcess: () => killBackgroundProcess,
44776
46563
  listDirectory: () => listDirectory,
@@ -44781,6 +46568,7 @@ __export(tools_exports, {
44781
46568
  parseRepository: () => parseRepository,
44782
46569
  readFile: () => readFile5,
44783
46570
  resetDefaultDriver: () => resetDefaultDriver,
46571
+ resolveFileEndpoints: () => resolveFileEndpoints,
44784
46572
  resolveMeetingId: () => resolveMeetingId,
44785
46573
  resolveRepository: () => resolveRepository,
44786
46574
  setMediaOutputHandler: () => setMediaOutputHandler,
@@ -49033,6 +50821,87 @@ async function resolveMeetingId(connector, input, prefix, effectiveUserId, effec
49033
50821
  subject: meetings.value[0].subject
49034
50822
  };
49035
50823
  }
50824
+ var DEFAULT_MAX_FILE_SIZE_BYTES = 50 * 1024 * 1024;
50825
+ var DEFAULT_FILE_SIZE_LIMITS = {
50826
+ ".pptx": 100 * 1024 * 1024,
50827
+ // 100 MB — presentations are image-heavy
50828
+ ".ppt": 100 * 1024 * 1024,
50829
+ ".odp": 100 * 1024 * 1024
50830
+ };
50831
+ function getFileSizeLimit(ext, overrides, defaultLimit) {
50832
+ const merged = { ...DEFAULT_FILE_SIZE_LIMITS, ...overrides };
50833
+ return merged[ext.toLowerCase()] ?? defaultLimit ?? DEFAULT_MAX_FILE_SIZE_BYTES;
50834
+ }
50835
+ var SUPPORTED_EXTENSIONS = /* @__PURE__ */ new Set([
50836
+ ".docx",
50837
+ ".pptx",
50838
+ ".xlsx",
50839
+ ".csv",
50840
+ ".pdf",
50841
+ ".odt",
50842
+ ".odp",
50843
+ ".ods",
50844
+ ".rtf",
50845
+ ".html",
50846
+ ".htm",
50847
+ ".txt",
50848
+ ".md",
50849
+ ".json",
50850
+ ".xml",
50851
+ ".yaml",
50852
+ ".yml"
50853
+ ]);
50854
+ function encodeSharingUrl(webUrl) {
50855
+ const base64 = Buffer.from(webUrl, "utf-8").toString("base64").replace(/=+$/, "").replace(/\//g, "_").replace(/\+/g, "-");
50856
+ return `u!${base64}`;
50857
+ }
50858
+ function isWebUrl(source) {
50859
+ return /^https?:\/\//i.test(source.trim());
50860
+ }
50861
+ function isMicrosoftFileUrl(source) {
50862
+ try {
50863
+ const url2 = new URL(source.trim());
50864
+ const host = url2.hostname.toLowerCase();
50865
+ return host.endsWith(".sharepoint.com") || host.endsWith(".sharepoint-df.com") || host === "onedrive.live.com" || host === "1drv.ms";
50866
+ } catch {
50867
+ return false;
50868
+ }
50869
+ }
50870
+ function getDrivePrefix(userPrefix, options) {
50871
+ if (options?.siteId) return `/sites/${options.siteId}/drive`;
50872
+ if (options?.driveId) return `/drives/${options.driveId}`;
50873
+ return `${userPrefix}/drive`;
50874
+ }
50875
+ function resolveFileEndpoints(source, drivePrefix) {
50876
+ const trimmed = source.trim();
50877
+ if (isWebUrl(trimmed)) {
50878
+ const token = encodeSharingUrl(trimmed);
50879
+ return {
50880
+ metadataEndpoint: `/shares/${token}/driveItem`,
50881
+ contentEndpoint: `/shares/${token}/driveItem/content`,
50882
+ isSharedUrl: true
50883
+ };
50884
+ }
50885
+ if (trimmed.startsWith("/")) {
50886
+ const path6 = trimmed.endsWith("/") ? trimmed.slice(0, -1) : trimmed;
50887
+ return {
50888
+ metadataEndpoint: `${drivePrefix}/root:${path6}:`,
50889
+ contentEndpoint: `${drivePrefix}/root:${path6}:/content`,
50890
+ isSharedUrl: false
50891
+ };
50892
+ }
50893
+ return {
50894
+ metadataEndpoint: `${drivePrefix}/items/${trimmed}`,
50895
+ contentEndpoint: `${drivePrefix}/items/${trimmed}/content`,
50896
+ isSharedUrl: false
50897
+ };
50898
+ }
50899
+ function formatFileSize(bytes) {
50900
+ if (bytes < 1024) return `${bytes} B`;
50901
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
50902
+ if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
50903
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
50904
+ }
49036
50905
 
49037
50906
  // src/tools/microsoft/createDraftEmail.ts
49038
50907
  function createDraftEmailTool(connector, userId) {
@@ -49738,16 +51607,539 @@ EXAMPLES:
49738
51607
  };
49739
51608
  }
49740
51609
 
51610
+ // src/tools/microsoft/readFile.ts
51611
+ function createMicrosoftReadFileTool(connector, userId, config) {
51612
+ const reader = DocumentReader.create();
51613
+ const defaultLimit = config?.maxFileSizeBytes ?? DEFAULT_MAX_FILE_SIZE_BYTES;
51614
+ const sizeOverrides = config?.fileSizeLimits;
51615
+ return {
51616
+ definition: {
51617
+ type: "function",
51618
+ function: {
51619
+ name: "read_file",
51620
+ description: `Read a file from Microsoft OneDrive or SharePoint and return its content as **markdown text**.
51621
+
51622
+ Use this tool when you need to read the contents of a document stored in OneDrive or SharePoint. The file is downloaded and automatically converted to clean markdown \u2014 you will never receive raw binary data.
51623
+
51624
+ **Supported formats:** .docx, .pptx, .xlsx, .csv, .pdf, .odt, .odp, .ods, .rtf, .html, .txt, .md, .json, .xml, .yaml
51625
+ **Not supported:** .doc, .xls, .ppt (legacy Office formats), images, videos, executables.
51626
+
51627
+ **The \`source\` parameter is flexible \u2014 you can provide any of these:**
51628
+ - A SharePoint or OneDrive **web URL**: \`https://contoso.sharepoint.com/sites/team/Shared Documents/report.docx\`
51629
+ - A OneDrive **sharing link**: \`https://1drv.ms/w/s!AmVg...\`
51630
+ - A **file path** within the drive: \`/Documents/Q4 Report.docx\`
51631
+ - A Graph API **item ID**: \`01ABCDEF123456\`
51632
+
51633
+ When given a web URL, the tool automatically resolves it to the correct Graph API call \u2014 no need to manually extract drive or item IDs.
51634
+
51635
+ **Maximum file size:** 50 MB (100 MB for presentations). For larger files, use the search or list tools to find smaller alternatives.
51636
+
51637
+ **Returns:** The file content as markdown text, along with metadata (filename, size, MIME type, webUrl). For spreadsheets, each sheet becomes a markdown table. For presentations, each slide becomes a section.`,
51638
+ parameters: {
51639
+ type: "object",
51640
+ properties: {
51641
+ source: {
51642
+ type: "string",
51643
+ description: 'The file to read. Accepts a web URL (SharePoint/OneDrive link), a file path within the drive (e.g., "/Documents/report.docx"), or a Graph API item ID.'
51644
+ },
51645
+ driveId: {
51646
+ type: "string",
51647
+ description: "Optional drive ID. Omit to use the default drive. Only needed when accessing a specific non-default drive."
51648
+ },
51649
+ siteId: {
51650
+ type: "string",
51651
+ description: "Optional SharePoint site ID. Use this instead of driveId to access files in a specific SharePoint site's default drive."
51652
+ },
51653
+ targetUser: {
51654
+ type: "string",
51655
+ description: `User ID or email (e.g., "user@contoso.com"). Only needed with application-only (client_credentials) auth to specify which user's drive to access.`
51656
+ }
51657
+ },
51658
+ required: ["source"]
51659
+ }
51660
+ },
51661
+ blocking: true,
51662
+ timeout: 6e4
51663
+ },
51664
+ describeCall: (args) => {
51665
+ const src = args.source.length > 80 ? args.source.slice(0, 77) + "..." : args.source;
51666
+ return `Read: ${src}`;
51667
+ },
51668
+ permission: {
51669
+ scope: "session",
51670
+ riskLevel: "low",
51671
+ approvalMessage: `Read a file from OneDrive/SharePoint via ${connector.displayName}`
51672
+ },
51673
+ execute: async (args, context) => {
51674
+ const effectiveUserId = context?.userId ?? userId;
51675
+ const effectiveAccountId = context?.accountId;
51676
+ if (!args.source || !args.source.trim()) {
51677
+ return {
51678
+ success: false,
51679
+ error: 'The "source" parameter is required. Provide a file URL, path, or item ID.'
51680
+ };
51681
+ }
51682
+ if (isWebUrl(args.source) && !isMicrosoftFileUrl(args.source)) {
51683
+ return {
51684
+ success: false,
51685
+ error: `The URL "${args.source}" does not appear to be a SharePoint or OneDrive link. This tool only supports URLs from *.sharepoint.com, onedrive.live.com, or 1drv.ms. For other URLs, use the web_fetch tool instead.`
51686
+ };
51687
+ }
51688
+ try {
51689
+ const userPrefix = getUserPathPrefix(connector, args.targetUser);
51690
+ const drivePrefix = getDrivePrefix(userPrefix, {
51691
+ siteId: args.siteId,
51692
+ driveId: args.driveId
51693
+ });
51694
+ const { metadataEndpoint, contentEndpoint, isSharedUrl } = resolveFileEndpoints(
51695
+ args.source,
51696
+ drivePrefix
51697
+ );
51698
+ const metadataQueryParams = isSharedUrl ? {} : { "$select": "id,name,size,file,folder,webUrl,parentReference" };
51699
+ const metadata = await microsoftFetch(connector, metadataEndpoint, {
51700
+ userId: effectiveUserId,
51701
+ accountId: effectiveAccountId,
51702
+ queryParams: metadataQueryParams
51703
+ });
51704
+ if (metadata.folder) {
51705
+ return {
51706
+ success: false,
51707
+ error: `"${metadata.name}" is a folder, not a file. Use the list_files tool to browse folder contents.`
51708
+ };
51709
+ }
51710
+ const ext = getExtension(metadata.name);
51711
+ const sizeLimit = getFileSizeLimit(ext, sizeOverrides, defaultLimit);
51712
+ if (metadata.size > sizeLimit) {
51713
+ return {
51714
+ success: false,
51715
+ filename: metadata.name,
51716
+ sizeBytes: metadata.size,
51717
+ error: `File "${metadata.name}" is ${formatFileSize(metadata.size)}, which exceeds the ${formatFileSize(sizeLimit)} limit for ${ext || "this file type"}. Consider downloading a smaller version or using the search tool to find an alternative.`
51718
+ };
51719
+ }
51720
+ if (ext && !SUPPORTED_EXTENSIONS.has(ext) && !FormatDetector.isBinaryDocumentFormat(ext)) {
51721
+ return {
51722
+ success: false,
51723
+ filename: metadata.name,
51724
+ mimeType: metadata.file?.mimeType,
51725
+ error: `File format "${ext}" is not supported for text extraction. Supported formats: ${[...SUPPORTED_EXTENSIONS].join(", ")}`
51726
+ };
51727
+ }
51728
+ const response = await connector.fetch(
51729
+ contentEndpoint,
51730
+ { method: "GET" },
51731
+ effectiveUserId,
51732
+ effectiveAccountId
51733
+ );
51734
+ if (!response.ok) {
51735
+ throw new MicrosoftAPIError(response.status, response.statusText, await response.text());
51736
+ }
51737
+ const arrayBuffer = await response.arrayBuffer();
51738
+ const buffer = Buffer.from(arrayBuffer);
51739
+ const result = await reader.read(
51740
+ { type: "buffer", buffer, filename: metadata.name },
51741
+ { extractImages: false }
51742
+ );
51743
+ if (!result.success) {
51744
+ return {
51745
+ success: false,
51746
+ filename: metadata.name,
51747
+ error: `Failed to convert "${metadata.name}" to markdown: ${result.error ?? "unknown error"}`
51748
+ };
51749
+ }
51750
+ const markdown = mergeTextPieces(result.pieces);
51751
+ if (!markdown || markdown.trim().length === 0) {
51752
+ return {
51753
+ success: true,
51754
+ filename: metadata.name,
51755
+ sizeBytes: metadata.size,
51756
+ mimeType: metadata.file?.mimeType,
51757
+ webUrl: metadata.webUrl,
51758
+ markdown: "*(empty document \u2014 no text content found)*"
51759
+ };
51760
+ }
51761
+ return {
51762
+ success: true,
51763
+ filename: metadata.name,
51764
+ sizeBytes: metadata.size,
51765
+ mimeType: metadata.file?.mimeType,
51766
+ webUrl: metadata.webUrl,
51767
+ markdown
51768
+ };
51769
+ } catch (error) {
51770
+ if (error instanceof MicrosoftAPIError) {
51771
+ if (error.status === 404) {
51772
+ return {
51773
+ success: false,
51774
+ error: `File not found. Check that the source "${args.source}" is correct and you have access.`
51775
+ };
51776
+ }
51777
+ if (error.status === 403 || error.status === 401) {
51778
+ return {
51779
+ success: false,
51780
+ error: `Access denied. The connector may not have sufficient permissions (Files.Read or Sites.Read.All required).`
51781
+ };
51782
+ }
51783
+ }
51784
+ return {
51785
+ success: false,
51786
+ error: `Failed to read file: ${error instanceof Error ? error.message : String(error)}`
51787
+ };
51788
+ }
51789
+ }
51790
+ };
51791
+ }
51792
+ function getExtension(filename) {
51793
+ const dot = filename.lastIndexOf(".");
51794
+ if (dot < 0) return "";
51795
+ return filename.slice(dot).toLowerCase();
51796
+ }
51797
+
51798
+ // src/tools/microsoft/listFiles.ts
51799
+ var DEFAULT_LIMIT = 50;
51800
+ var MAX_LIMIT = 200;
51801
+ var SELECT_FIELDS = "id,name,size,lastModifiedDateTime,file,folder,webUrl";
51802
+ function createMicrosoftListFilesTool(connector, userId) {
51803
+ return {
51804
+ definition: {
51805
+ type: "function",
51806
+ function: {
51807
+ name: "list_files",
51808
+ description: `List files and folders in a Microsoft OneDrive or SharePoint directory.
51809
+
51810
+ Use this tool to browse the contents of a folder, discover available files before reading them, or search within a specific folder. Returns file/folder metadata (name, size, type, last modified, URL) \u2014 **never returns file contents**.
51811
+
51812
+ **The \`path\` parameter is flexible \u2014 you can provide:**
51813
+ - A **folder path** within the drive: \`/Documents/Projects\` or \`/\` for root
51814
+ - A SharePoint/OneDrive **folder URL**: \`https://contoso.sharepoint.com/sites/team/Shared Documents/Projects\`
51815
+ - Omit entirely to list the root of the drive
51816
+
51817
+ **Optional search:** Use the \`search\` parameter to filter by filename within the folder tree. This uses Microsoft's server-side search (fast, works across subfolders).
51818
+
51819
+ **Tip:** To read a file's content after finding it, use the read_file tool with the file's webUrl or id from these results.`,
51820
+ parameters: {
51821
+ type: "object",
51822
+ properties: {
51823
+ path: {
51824
+ type: "string",
51825
+ description: 'Folder path (e.g., "/Documents/Projects") or a web URL to a SharePoint/OneDrive folder. Omit to list root.'
51826
+ },
51827
+ driveId: {
51828
+ type: "string",
51829
+ description: "Optional drive ID for a specific non-default drive."
51830
+ },
51831
+ siteId: {
51832
+ type: "string",
51833
+ description: "Optional SharePoint site ID to access that site's default document library."
51834
+ },
51835
+ search: {
51836
+ type: "string",
51837
+ description: "Optional search query to filter results by filename or content. Searches within the specified folder and all subfolders."
51838
+ },
51839
+ limit: {
51840
+ type: "number",
51841
+ description: `Maximum number of items to return (default: ${DEFAULT_LIMIT}, max: ${MAX_LIMIT}).`
51842
+ },
51843
+ targetUser: {
51844
+ type: "string",
51845
+ description: "User ID or email. Only needed with application-only (client_credentials) auth."
51846
+ }
51847
+ }
51848
+ }
51849
+ },
51850
+ blocking: true,
51851
+ timeout: 3e4
51852
+ },
51853
+ describeCall: (args) => {
51854
+ const loc = args.path || "/";
51855
+ const suffix = args.search ? ` (search: "${args.search}")` : "";
51856
+ return `List: ${loc}${suffix}`;
51857
+ },
51858
+ permission: {
51859
+ scope: "session",
51860
+ riskLevel: "low",
51861
+ approvalMessage: `List files in OneDrive/SharePoint via ${connector.displayName}`
51862
+ },
51863
+ execute: async (args, context) => {
51864
+ const effectiveUserId = context?.userId ?? userId;
51865
+ const effectiveAccountId = context?.accountId;
51866
+ const limit = Math.min(Math.max(1, args.limit ?? DEFAULT_LIMIT), MAX_LIMIT);
51867
+ try {
51868
+ const userPrefix = getUserPathPrefix(connector, args.targetUser);
51869
+ const drivePrefix = getDrivePrefix(userPrefix, {
51870
+ siteId: args.siteId,
51871
+ driveId: args.driveId
51872
+ });
51873
+ let endpoint;
51874
+ if (args.search) {
51875
+ const folderBase = args.path && !isWebUrl(args.path) ? `${drivePrefix}/root:${normalizePath(args.path)}:` : drivePrefix;
51876
+ endpoint = `${folderBase}/search(q='${encodeSearchQuery(args.search)}')`;
51877
+ } else if (args.path) {
51878
+ if (isWebUrl(args.path)) {
51879
+ const token = encodeSharingUrl(args.path.trim());
51880
+ endpoint = `/shares/${token}/driveItem/children`;
51881
+ } else {
51882
+ const path6 = normalizePath(args.path);
51883
+ endpoint = path6 === "/" ? `${drivePrefix}/root/children` : `${drivePrefix}/root:${path6}:/children`;
51884
+ }
51885
+ } else {
51886
+ endpoint = `${drivePrefix}/root/children`;
51887
+ }
51888
+ const queryParams = {
51889
+ "$top": limit,
51890
+ "$select": SELECT_FIELDS
51891
+ };
51892
+ if (!args.search) {
51893
+ queryParams["$orderby"] = "name asc";
51894
+ }
51895
+ const data = await microsoftFetch(connector, endpoint, {
51896
+ userId: effectiveUserId,
51897
+ accountId: effectiveAccountId,
51898
+ queryParams
51899
+ });
51900
+ const items = (data.value || []).map(mapDriveItem);
51901
+ return {
51902
+ success: true,
51903
+ items,
51904
+ totalCount: items.length,
51905
+ hasMore: !!data["@odata.nextLink"]
51906
+ };
51907
+ } catch (error) {
51908
+ return {
51909
+ success: false,
51910
+ error: `Failed to list files: ${error instanceof Error ? error.message : String(error)}`
51911
+ };
51912
+ }
51913
+ }
51914
+ };
51915
+ }
51916
+ function mapDriveItem(item) {
51917
+ return {
51918
+ name: item.name,
51919
+ type: item.folder ? "folder" : "file",
51920
+ size: item.size,
51921
+ sizeFormatted: formatFileSize(item.size),
51922
+ mimeType: item.file?.mimeType,
51923
+ lastModified: item.lastModifiedDateTime,
51924
+ webUrl: item.webUrl,
51925
+ id: item.id,
51926
+ childCount: item.folder?.childCount
51927
+ };
51928
+ }
51929
+ function normalizePath(path6) {
51930
+ let p = path6.trim();
51931
+ if (!p.startsWith("/")) p = "/" + p;
51932
+ if (p.endsWith("/") && p.length > 1) p = p.slice(0, -1);
51933
+ return p;
51934
+ }
51935
+ function encodeSearchQuery(query) {
51936
+ return query.replace(/\\/g, "\\\\").replace(/'/g, "''");
51937
+ }
51938
+
51939
+ // src/tools/microsoft/searchFiles.ts
51940
+ var DEFAULT_LIMIT2 = 25;
51941
+ var MAX_LIMIT2 = 100;
51942
+ function createMicrosoftSearchFilesTool(connector, userId) {
51943
+ return {
51944
+ definition: {
51945
+ type: "function",
51946
+ function: {
51947
+ name: "search_files",
51948
+ description: `Search for files across Microsoft OneDrive and SharePoint.
51949
+
51950
+ Use this tool when you need to **find** files by name, content, or metadata across all of the user's OneDrive and SharePoint sites. This is a powerful full-text search \u2014 it searches inside document content, not just filenames.
51951
+
51952
+ **Supports Microsoft's KQL (Keyword Query Language):**
51953
+ - Simple text: \`"quarterly report"\`
51954
+ - By filename: \`filename:budget.xlsx\`
51955
+ - By author: \`author:"Jane Smith"\`
51956
+ - By date: \`lastModifiedTime>2024-01-01\`
51957
+ - Combined: \`project proposal filetype:docx\`
51958
+
51959
+ **Filter by file type:** Use the \`fileTypes\` parameter (e.g., \`["docx", "pdf"]\`) to restrict results to specific formats.
51960
+
51961
+ **Limit to a SharePoint site:** Provide the \`siteId\` parameter to search only within a specific site.
51962
+
51963
+ **Returns:** A list of matching files with name, path, site, snippet (text preview), size, and webUrl. Does NOT return file contents \u2014 use the read_file tool to read a specific result.
51964
+
51965
+ **Tip:** Start with a broad search, then use read_file on the most relevant results.`,
51966
+ parameters: {
51967
+ type: "object",
51968
+ properties: {
51969
+ query: {
51970
+ type: "string",
51971
+ description: 'Search query. Supports plain text and KQL syntax (e.g., "budget report", "filename:spec.docx", "author:john filetype:pptx").'
51972
+ },
51973
+ siteId: {
51974
+ type: "string",
51975
+ description: "Optional SharePoint site ID to limit search to a specific site."
51976
+ },
51977
+ fileTypes: {
51978
+ type: "array",
51979
+ items: { type: "string" },
51980
+ description: 'Optional file type filter. Array of extensions without dots (e.g., ["docx", "pdf", "xlsx"]).'
51981
+ },
51982
+ limit: {
51983
+ type: "number",
51984
+ description: `Maximum number of results (default: ${DEFAULT_LIMIT2}, max: ${MAX_LIMIT2}).`
51985
+ },
51986
+ targetUser: {
51987
+ type: "string",
51988
+ description: "User ID or email. Only needed with application-only (client_credentials) auth."
51989
+ }
51990
+ },
51991
+ required: ["query"]
51992
+ }
51993
+ },
51994
+ blocking: true,
51995
+ timeout: 3e4
51996
+ },
51997
+ describeCall: (args) => {
51998
+ const types = args.fileTypes?.length ? ` [${args.fileTypes.join(",")}]` : "";
51999
+ return `Search: "${args.query}"${types}`;
52000
+ },
52001
+ permission: {
52002
+ scope: "session",
52003
+ riskLevel: "low",
52004
+ approvalMessage: `Search files in OneDrive/SharePoint via ${connector.displayName}`
52005
+ },
52006
+ execute: async (args, context) => {
52007
+ const effectiveUserId = context?.userId ?? userId;
52008
+ const effectiveAccountId = context?.accountId;
52009
+ const limit = Math.min(Math.max(1, args.limit ?? DEFAULT_LIMIT2), MAX_LIMIT2);
52010
+ try {
52011
+ if (args.siteId) {
52012
+ return await searchViaDriveEndpoint(
52013
+ connector,
52014
+ args,
52015
+ limit,
52016
+ effectiveUserId,
52017
+ effectiveAccountId
52018
+ );
52019
+ }
52020
+ let queryString = args.query;
52021
+ if (args.fileTypes?.length) {
52022
+ const typeFilter = args.fileTypes.map((t) => `filetype:${t.replace(/^\./, "")}`).join(" OR ");
52023
+ queryString = `(${queryString}) (${typeFilter})`;
52024
+ }
52025
+ const searchRequest = {
52026
+ requests: [
52027
+ {
52028
+ entityTypes: ["driveItem"],
52029
+ query: { queryString },
52030
+ from: 0,
52031
+ size: limit,
52032
+ fields: [
52033
+ "id",
52034
+ "name",
52035
+ "size",
52036
+ "webUrl",
52037
+ "lastModifiedDateTime",
52038
+ "parentReference",
52039
+ "file"
52040
+ ]
52041
+ }
52042
+ ]
52043
+ };
52044
+ const data = await microsoftFetch(connector, "/search/query", {
52045
+ method: "POST",
52046
+ body: searchRequest,
52047
+ userId: effectiveUserId,
52048
+ accountId: effectiveAccountId
52049
+ });
52050
+ const container = data.value?.[0]?.hitsContainers?.[0];
52051
+ if (!container?.hits?.length) {
52052
+ return {
52053
+ success: true,
52054
+ results: [],
52055
+ totalCount: 0,
52056
+ hasMore: false
52057
+ };
52058
+ }
52059
+ const results = container.hits.map((hit) => {
52060
+ const resource = hit.resource;
52061
+ return {
52062
+ name: resource.name,
52063
+ path: resource.parentReference?.path?.replace(/\/drive\/root:/, "") || void 0,
52064
+ site: resource.parentReference?.siteId || void 0,
52065
+ snippet: hit.summary || void 0,
52066
+ size: resource.size,
52067
+ sizeFormatted: formatFileSize(resource.size),
52068
+ webUrl: resource.webUrl,
52069
+ id: resource.id,
52070
+ lastModified: resource.lastModifiedDateTime
52071
+ };
52072
+ });
52073
+ return {
52074
+ success: true,
52075
+ results,
52076
+ totalCount: container.total,
52077
+ hasMore: container.moreResultsAvailable
52078
+ };
52079
+ } catch (error) {
52080
+ return {
52081
+ success: false,
52082
+ error: `Search failed: ${error instanceof Error ? error.message : String(error)}`
52083
+ };
52084
+ }
52085
+ }
52086
+ };
52087
+ }
52088
+ async function searchViaDriveEndpoint(connector, args, limit, effectiveUserId, effectiveAccountId) {
52089
+ const drivePrefix = `/sites/${args.siteId}/drive`;
52090
+ const escapedQuery = args.query.replace(/'/g, "''");
52091
+ const endpoint = `${drivePrefix}/root/search(q='${escapedQuery}')`;
52092
+ const data = await microsoftFetch(connector, endpoint, {
52093
+ userId: effectiveUserId,
52094
+ accountId: effectiveAccountId,
52095
+ queryParams: {
52096
+ "$top": limit,
52097
+ "$select": "id,name,size,webUrl,lastModifiedDateTime,parentReference,file"
52098
+ }
52099
+ });
52100
+ let items = data.value || [];
52101
+ if (args.fileTypes?.length) {
52102
+ const extSet = new Set(args.fileTypes.map((t) => t.replace(/^\./, "").toLowerCase()));
52103
+ items = items.filter((item) => {
52104
+ const ext = item.name.split(".").pop()?.toLowerCase();
52105
+ return ext && extSet.has(ext);
52106
+ });
52107
+ }
52108
+ const results = items.map((item) => ({
52109
+ name: item.name,
52110
+ path: item.parentReference?.path?.replace(/\/drive\/root:/, "") || void 0,
52111
+ site: item.parentReference?.siteId || void 0,
52112
+ snippet: void 0,
52113
+ size: item.size,
52114
+ sizeFormatted: formatFileSize(item.size),
52115
+ webUrl: item.webUrl,
52116
+ id: item.id,
52117
+ lastModified: item.lastModifiedDateTime
52118
+ }));
52119
+ return {
52120
+ success: true,
52121
+ results,
52122
+ totalCount: results.length,
52123
+ hasMore: !!data["@odata.nextLink"]
52124
+ };
52125
+ }
52126
+
49741
52127
  // src/tools/microsoft/register.ts
49742
52128
  function registerMicrosoftTools() {
49743
52129
  ConnectorTools.registerService("microsoft", (connector, userId) => {
49744
52130
  return [
52131
+ // Email
49745
52132
  createDraftEmailTool(connector, userId),
49746
52133
  createSendEmailTool(connector, userId),
52134
+ // Meetings
49747
52135
  createMeetingTool(connector, userId),
49748
52136
  createEditMeetingTool(connector, userId),
49749
52137
  createGetMeetingTranscriptTool(connector, userId),
49750
- createFindMeetingSlotsTool(connector, userId)
52138
+ createFindMeetingSlotsTool(connector, userId),
52139
+ // Files (OneDrive / SharePoint)
52140
+ createMicrosoftReadFileTool(connector, userId),
52141
+ createMicrosoftListFilesTool(connector, userId),
52142
+ createMicrosoftSearchFilesTool(connector, userId)
49751
52143
  ];
49752
52144
  });
49753
52145
  }
@@ -51101,6 +53493,311 @@ function createCustomToolTest() {
51101
53493
  }
51102
53494
  var customToolTest = createCustomToolTest();
51103
53495
 
53496
+ // src/tools/routines/resolveStorage.ts
53497
+ init_StorageRegistry();
53498
+ function buildStorageContext3(toolContext) {
53499
+ const global2 = StorageRegistry.getContext();
53500
+ if (global2) return global2;
53501
+ if (toolContext?.userId) return { userId: toolContext.userId };
53502
+ return void 0;
53503
+ }
53504
+ function resolveRoutineDefinitionStorage(explicit, toolContext) {
53505
+ if (explicit) return explicit;
53506
+ const factory = StorageRegistry.get("routineDefinitions");
53507
+ if (factory) {
53508
+ return factory(buildStorageContext3(toolContext));
53509
+ }
53510
+ return new FileRoutineDefinitionStorage();
53511
+ }
53512
+
53513
+ // src/tools/routines/generateRoutine.ts
53514
+ var TOOL_DESCRIPTION = `Generate and save a complete routine definition to persistent storage. A routine is a reusable, parameterized workflow template composed of tasks with dependencies, control flow, and validation.
53515
+
53516
+ ## Routine Structure
53517
+
53518
+ A routine definition has these fields:
53519
+ - **name** (required): Human-readable name (e.g., "Research and Summarize Topic")
53520
+ - **description** (required): What this routine accomplishes
53521
+ - **version**: Semver string for tracking evolution (e.g., "1.0.0")
53522
+ - **author**: Creator name or identifier
53523
+ - **tags**: Array of strings for categorization and filtering (e.g., ["research", "analysis"])
53524
+ - **instructions**: Additional text injected into the agent's system prompt when executing this routine. Use for behavioral guidance, tone, constraints.
53525
+ - **parameters**: Array of input parameters that make the routine reusable. Each has: name, description, required (default false), default. Use {{param.NAME}} in task descriptions/expectedOutput to reference them.
53526
+ - **requiredTools**: Tool names that must be available before starting (e.g., ["web_fetch", "memory_store"])
53527
+ - **requiredPlugins**: Plugin names that must be enabled (e.g., ["working_memory"])
53528
+ - **concurrency**: { maxParallelTasks: number, strategy: "fifo"|"priority"|"shortest-first", failureMode?: "fail-fast"|"continue"|"fail-all" }
53529
+ - **allowDynamicTasks**: If true, the LLM can add/modify tasks during execution (default: false)
53530
+ - **tasks** (required): Array of TaskInput objects defining the workflow steps
53531
+
53532
+ ## Task Structure
53533
+
53534
+ Each task in the tasks array has:
53535
+ - **name** (required): Unique name within the routine (used for dependency references)
53536
+ - **description** (required): What this task should accomplish \u2014 the agent uses this as its goal
53537
+ - **dependsOn**: Array of task names that must complete before this task starts (e.g., ["Gather Data", "Validate Input"])
53538
+ - **suggestedTools**: Tool names the agent should prefer for this task (advisory, not enforced)
53539
+ - **expectedOutput**: Description of what the task should produce \u2014 helps the agent know when it's done
53540
+ - **maxAttempts**: Max retry count on failure (default: 3)
53541
+ - **condition**: Execute only if a condition is met (see Conditions below)
53542
+ - **controlFlow**: Map, fold, or until loop (see Control Flow below)
53543
+ - **validation**: Completion validation settings (see Validation below)
53544
+ - **execution**: { parallel?: boolean, maxConcurrency?: number, priority?: number, maxIterations?: number }
53545
+ - **metadata**: Arbitrary key-value pairs for extensions
53546
+
53547
+ ## Control Flow Types
53548
+
53549
+ Tasks can have a controlFlow field for iteration patterns:
53550
+
53551
+ ### map \u2014 Iterate over an array, run sub-tasks per element
53552
+ \`\`\`json
53553
+ {
53554
+ "type": "map",
53555
+ "source": { "task": "Fetch Items" },
53556
+ "tasks": [
53557
+ { "name": "Process Item", "description": "Process {{map.item}} ({{map.index}}/{{map.total}})" }
53558
+ ],
53559
+ "resultKey": "processed_items",
53560
+ "maxIterations": 100,
53561
+ "iterationTimeoutMs": 30000
53562
+ }
53563
+ \`\`\`
53564
+
53565
+ ### fold \u2014 Accumulate a result across array elements
53566
+ \`\`\`json
53567
+ {
53568
+ "type": "fold",
53569
+ "source": { "key": "data_points", "path": "results" },
53570
+ "tasks": [
53571
+ { "name": "Merge Entry", "description": "Merge {{map.item}} into {{fold.accumulator}}" }
53572
+ ],
53573
+ "initialValue": "",
53574
+ "resultKey": "merged_result",
53575
+ "maxIterations": 50
53576
+ }
53577
+ \`\`\`
53578
+
53579
+ ### until \u2014 Loop until a condition is met
53580
+ \`\`\`json
53581
+ {
53582
+ "type": "until",
53583
+ "tasks": [
53584
+ { "name": "Refine Draft", "description": "Improve the current draft based on feedback" }
53585
+ ],
53586
+ "condition": { "memoryKey": "quality_score", "operator": "greater_than", "value": 80, "onFalse": "skip" },
53587
+ "maxIterations": 5,
53588
+ "iterationKey": "refinement_round"
53589
+ }
53590
+ \`\`\`
53591
+
53592
+ ### Source field
53593
+ The \`source\` field in map/fold can be:
53594
+ - A string: direct memory key lookup (e.g., "my_items")
53595
+ - \`{ task: "TaskName" }\`: resolves the output of a completed dependency task
53596
+ - \`{ key: "memoryKey" }\`: direct memory key lookup
53597
+ - \`{ key: "memoryKey", path: "data.items" }\`: memory key with JSON path extraction
53598
+
53599
+ ### Sub-routine tasks
53600
+ Tasks inside controlFlow.tasks have the same shape as regular tasks (name, description, dependsOn, suggestedTools, etc.) but execute within the control flow context.
53601
+
53602
+ ## Template Placeholders
53603
+
53604
+ Use these in task descriptions and expectedOutput:
53605
+ - \`{{param.NAME}}\` \u2014 routine parameter value
53606
+ - \`{{map.item}}\` \u2014 current element in map/fold iteration
53607
+ - \`{{map.index}}\` \u2014 current 0-based iteration index
53608
+ - \`{{map.total}}\` \u2014 total number of elements
53609
+ - \`{{fold.accumulator}}\` \u2014 current accumulated value in fold
53610
+
53611
+ ## Task Conditions
53612
+
53613
+ Execute a task conditionally based on memory state:
53614
+ \`\`\`json
53615
+ {
53616
+ "memoryKey": "user_preference",
53617
+ "operator": "equals",
53618
+ "value": "detailed",
53619
+ "onFalse": "skip"
53620
+ }
53621
+ \`\`\`
53622
+
53623
+ Operators: "exists", "not_exists", "equals", "contains", "truthy", "greater_than", "less_than"
53624
+ onFalse actions: "skip" (mark skipped), "fail" (mark failed), "wait" (block until condition met)
53625
+
53626
+ ## Validation
53627
+
53628
+ Enable completion validation to verify task quality:
53629
+ \`\`\`json
53630
+ {
53631
+ "skipReflection": false,
53632
+ "completionCriteria": [
53633
+ "Response contains at least 3 specific examples",
53634
+ "All requested sections are present"
53635
+ ],
53636
+ "minCompletionScore": 80,
53637
+ "requiredMemoryKeys": ["result_data"],
53638
+ "mode": "strict"
53639
+ }
53640
+ \`\`\`
53641
+
53642
+ By default, skipReflection is true (validation auto-passes). Set to false and provide completionCriteria to enable LLM self-reflection validation.
53643
+
53644
+ ## Best Practices
53645
+
53646
+ 1. **Task naming**: Use clear, action-oriented names (e.g., "Research Topic", "Generate Summary"). Names are used in dependsOn references.
53647
+ 2. **Dependency chaining**: Build pipelines by having each task depend on the previous one. Independent tasks can run in parallel.
53648
+ 3. **Control flow vs sequential**: Use map/fold when iterating over dynamic data. Use sequential tasks for fixed multi-step workflows.
53649
+ 4. **Parameters**: Define parameters for any value that should vary between executions. Always provide a description.
53650
+ 5. **Instructions**: Use the instructions field for behavioral guidance that applies to all tasks in the routine.
53651
+ 6. **Keep tasks focused**: Each task should have a single clear goal. Break complex work into multiple dependent tasks.
53652
+ 7. **Expected output**: Always specify expectedOutput \u2014 it helps the agent know when it's done and what format to produce.`;
53653
+ function createGenerateRoutine(storage) {
53654
+ return {
53655
+ definition: {
53656
+ type: "function",
53657
+ function: {
53658
+ name: "generate_routine",
53659
+ description: TOOL_DESCRIPTION,
53660
+ parameters: {
53661
+ type: "object",
53662
+ properties: {
53663
+ definition: {
53664
+ type: "object",
53665
+ description: "Complete routine definition input",
53666
+ properties: {
53667
+ name: { type: "string", description: "Human-readable routine name" },
53668
+ description: { type: "string", description: "What this routine accomplishes" },
53669
+ version: { type: "string", description: 'Semver version string (e.g., "1.0.0")' },
53670
+ author: { type: "string", description: "Creator name or identifier" },
53671
+ tags: { type: "array", items: { type: "string" }, description: "Tags for categorization" },
53672
+ instructions: { type: "string", description: "Additional instructions injected into system prompt during execution" },
53673
+ parameters: {
53674
+ type: "array",
53675
+ description: "Input parameters for reusable routines",
53676
+ items: {
53677
+ type: "object",
53678
+ properties: {
53679
+ name: { type: "string", description: "Parameter name (referenced as {{param.name}})" },
53680
+ description: { type: "string", description: "Human-readable description" },
53681
+ required: { type: "boolean", description: "Whether this parameter must be provided (default: false)" },
53682
+ default: { description: "Default value when not provided" }
53683
+ },
53684
+ required: ["name", "description"]
53685
+ }
53686
+ },
53687
+ requiredTools: { type: "array", items: { type: "string" }, description: "Tool names that must be available" },
53688
+ requiredPlugins: { type: "array", items: { type: "string" }, description: "Plugin names that must be enabled" },
53689
+ concurrency: {
53690
+ type: "object",
53691
+ description: "Concurrency settings for parallel task execution",
53692
+ properties: {
53693
+ maxParallelTasks: { type: "number", description: "Maximum tasks running in parallel" },
53694
+ strategy: { type: "string", enum: ["fifo", "priority", "shortest-first"], description: "Task selection strategy" },
53695
+ failureMode: { type: "string", enum: ["fail-fast", "continue", "fail-all"], description: "How to handle failures in parallel" }
53696
+ },
53697
+ required: ["maxParallelTasks", "strategy"]
53698
+ },
53699
+ allowDynamicTasks: { type: "boolean", description: "Allow LLM to add/modify tasks during execution (default: false)" },
53700
+ tasks: {
53701
+ type: "array",
53702
+ description: "Array of task definitions forming the workflow",
53703
+ items: {
53704
+ type: "object",
53705
+ properties: {
53706
+ name: { type: "string", description: "Unique task name (used in dependsOn references)" },
53707
+ description: { type: "string", description: "What this task should accomplish" },
53708
+ dependsOn: { type: "array", items: { type: "string" }, description: "Task names that must complete first" },
53709
+ suggestedTools: { type: "array", items: { type: "string" }, description: "Preferred tool names for this task" },
53710
+ expectedOutput: { type: "string", description: "Description of expected task output" },
53711
+ maxAttempts: { type: "number", description: "Max retries on failure (default: 3)" },
53712
+ condition: {
53713
+ type: "object",
53714
+ description: "Conditional execution based on memory state",
53715
+ properties: {
53716
+ memoryKey: { type: "string", description: "Memory key to check" },
53717
+ operator: { type: "string", enum: ["exists", "not_exists", "equals", "contains", "truthy", "greater_than", "less_than"] },
53718
+ value: { description: "Value to compare against" },
53719
+ onFalse: { type: "string", enum: ["skip", "fail", "wait"], description: "Action when condition is false" }
53720
+ },
53721
+ required: ["memoryKey", "operator", "onFalse"]
53722
+ },
53723
+ controlFlow: {
53724
+ type: "object",
53725
+ description: "Control flow for iteration (map, fold, or until)",
53726
+ properties: {
53727
+ type: { type: "string", enum: ["map", "fold", "until"], description: "Control flow type" },
53728
+ source: { description: 'Source data: string key, { task: "name" }, { key: "key" }, or { key: "key", path: "json.path" }' },
53729
+ tasks: { type: "array", description: "Sub-routine tasks to execute per iteration", items: { type: "object" } },
53730
+ resultKey: { type: "string", description: "Memory key for collected results" },
53731
+ initialValue: { description: "Starting accumulator value (fold only)" },
53732
+ condition: { type: "object", description: "Exit condition (until only)" },
53733
+ maxIterations: { type: "number", description: "Cap on iterations" },
53734
+ iterationKey: { type: "string", description: "Memory key for iteration index (until only)" },
53735
+ iterationTimeoutMs: { type: "number", description: "Timeout per iteration in ms" }
53736
+ },
53737
+ required: ["type", "tasks"]
53738
+ },
53739
+ validation: {
53740
+ type: "object",
53741
+ description: "Completion validation settings",
53742
+ properties: {
53743
+ skipReflection: { type: "boolean", description: "Set to false to enable LLM self-reflection validation" },
53744
+ completionCriteria: { type: "array", items: { type: "string" }, description: "Natural language criteria for completion" },
53745
+ minCompletionScore: { type: "number", description: "Minimum score (0-100) to pass (default: 80)" },
53746
+ requiredMemoryKeys: { type: "array", items: { type: "string" }, description: "Memory keys that must exist after completion" },
53747
+ mode: { type: "string", enum: ["strict", "warn"], description: "Validation failure mode (default: strict)" },
53748
+ requireUserApproval: { type: "string", enum: ["never", "uncertain", "always"], description: "When to ask user for approval" },
53749
+ customValidator: { type: "string", description: "Custom validation hook name" }
53750
+ }
53751
+ },
53752
+ execution: {
53753
+ type: "object",
53754
+ description: "Execution settings",
53755
+ properties: {
53756
+ parallel: { type: "boolean", description: "Can run in parallel with other parallel tasks" },
53757
+ maxConcurrency: { type: "number", description: "Max concurrent sub-work" },
53758
+ priority: { type: "number", description: "Higher = executed first" },
53759
+ maxIterations: { type: "number", description: "Max LLM iterations per task (default: 50)" }
53760
+ }
53761
+ },
53762
+ metadata: { type: "object", description: "Arbitrary key-value metadata" }
53763
+ },
53764
+ required: ["name", "description"]
53765
+ }
53766
+ },
53767
+ metadata: { type: "object", description: "Arbitrary routine-level metadata" }
53768
+ },
53769
+ required: ["name", "description", "tasks"]
53770
+ }
53771
+ },
53772
+ required: ["definition"]
53773
+ }
53774
+ }
53775
+ },
53776
+ permission: { scope: "session", riskLevel: "medium" },
53777
+ execute: async (args, context) => {
53778
+ try {
53779
+ const userId = context?.userId;
53780
+ const s = resolveRoutineDefinitionStorage(storage, context);
53781
+ const routineDefinition = createRoutineDefinition(args.definition);
53782
+ await s.save(userId, routineDefinition);
53783
+ return {
53784
+ success: true,
53785
+ id: routineDefinition.id,
53786
+ name: routineDefinition.name,
53787
+ storagePath: s.getPath(userId)
53788
+ };
53789
+ } catch (error) {
53790
+ return {
53791
+ success: false,
53792
+ error: error.message
53793
+ };
53794
+ }
53795
+ },
53796
+ describeCall: (args) => args.definition?.name ?? "routine"
53797
+ };
53798
+ }
53799
+ var generateRoutine = createGenerateRoutine();
53800
+
51104
53801
  // src/tools/registry.generated.ts
51105
53802
  var toolRegistry = [
51106
53803
  {
@@ -51328,6 +54025,15 @@ var toolRegistry = [
51328
54025
  tool: jsonManipulator,
51329
54026
  safeByDefault: true
51330
54027
  },
54028
+ {
54029
+ name: "generate_routine",
54030
+ exportName: "generateRoutine",
54031
+ displayName: "Generate Routine",
54032
+ category: "routines",
54033
+ description: "Complete routine definition input",
54034
+ tool: generateRoutine,
54035
+ safeByDefault: false
54036
+ },
51331
54037
  {
51332
54038
  name: "bash",
51333
54039
  exportName: "bash",
@@ -51757,6 +54463,127 @@ REMEMBER: Keep it conversational, ask one question at a time, and only output th
51757
54463
  }
51758
54464
  };
51759
54465
 
51760
- export { AGENT_DEFINITION_FORMAT_VERSION, AIError, APPROVAL_STATE_VERSION, Agent, AgentContextNextGen, ApproximateTokenEstimator, BaseMediaProvider, BasePluginNextGen, BaseProvider, BaseTextProvider, BraveProvider, CONNECTOR_CONFIG_VERSION, CONTEXT_SESSION_FORMAT_VERSION, CUSTOM_TOOL_DEFINITION_VERSION, CheckpointManager, CircuitBreaker, CircuitOpenError, Connector, ConnectorConfigStore, ConnectorTools, ConsoleMetrics, ContentType, ContextOverflowError, DEFAULT_ALLOWLIST, DEFAULT_BACKOFF_CONFIG, DEFAULT_BASE_DELAY_MS, DEFAULT_CHECKPOINT_STRATEGY, DEFAULT_CIRCUIT_BREAKER_CONFIG, DEFAULT_CONFIG2 as DEFAULT_CONFIG, DEFAULT_CONNECTOR_TIMEOUT, DEFAULT_CONTEXT_CONFIG, DEFAULT_DESKTOP_CONFIG, DEFAULT_FEATURES, DEFAULT_FILESYSTEM_CONFIG, DEFAULT_HISTORY_MANAGER_CONFIG, DEFAULT_MAX_DELAY_MS, DEFAULT_MAX_RETRIES, DEFAULT_MEMORY_CONFIG, DEFAULT_PERMISSION_CONFIG, DEFAULT_RATE_LIMITER_CONFIG, DEFAULT_RETRYABLE_STATUSES, DEFAULT_SHELL_CONFIG, DESKTOP_TOOL_NAMES, DefaultCompactionStrategy, DependencyCycleError, DocumentReader, ErrorHandler, ExecutionContext, ExternalDependencyHandler, FileAgentDefinitionStorage, FileConnectorStorage, FileContextStorage, FileCustomToolStorage, FileMediaStorage as FileMediaOutputHandler, FileMediaStorage, FilePersistentInstructionsStorage, FileRoutineDefinitionStorage, FileStorage, FileUserInfoStorage, FormatDetector, FrameworkLogger, HookManager, IMAGE_MODELS, IMAGE_MODEL_REGISTRY, ImageGeneration, InContextMemoryPluginNextGen, InMemoryAgentStateStorage, InMemoryHistoryStorage, InMemoryMetrics, InMemoryPlanStorage, InMemoryStorage, InvalidConfigError, InvalidToolArgumentsError, LLM_MODELS, LoggingPlugin, MCPClient, MCPConnectionError, MCPError, MCPProtocolError, MCPRegistry, MCPResourceError, MCPTimeoutError, MCPToolError, MEMORY_PRIORITY_VALUES, MODEL_REGISTRY, MemoryConnectorStorage, MemoryEvictionCompactor, MemoryStorage, MessageBuilder, MessageRole, ModelNotSupportedError, NoOpMetrics, NutTreeDriver, OAuthManager, ParallelTasksError, PersistentInstructionsPluginNextGen, PlanningAgent, ProviderAuthError, ProviderConfigAgent, ProviderContextLengthError, ProviderError, ProviderErrorMapper, ProviderNotFoundError, ProviderRateLimitError, ROUTINE_KEYS, RapidAPIProvider, RateLimitError, SERVICE_DEFINITIONS, SERVICE_INFO, SERVICE_URL_PATTERNS, SIMPLE_ICONS_CDN, STT_MODELS, STT_MODEL_REGISTRY, ScopedConnectorRegistry, ScrapeProvider, SearchProvider, SerperProvider, Services, SpeechToText, StorageRegistry, StrategyRegistry, StreamEventType, StreamHelpers, StreamState, SummarizeCompactor, TERMINAL_TASK_STATUSES, TTS_MODELS, TTS_MODEL_REGISTRY, TaskTimeoutError, TaskValidationError, TavilyProvider, TextToSpeech, TokenBucketRateLimiter, ToolCallState, ToolExecutionError, ToolExecutionPipeline, ToolManager, ToolNotFoundError, ToolPermissionManager, ToolRegistry, ToolTimeoutError, TruncateCompactor, UserInfoPluginNextGen, VENDORS, VENDOR_ICON_MAP, VIDEO_MODELS, VIDEO_MODEL_REGISTRY, Vendor, VideoGeneration, WorkingMemory, WorkingMemoryPluginNextGen, addJitter, allVendorTemplates, assertNotDestroyed, authenticatedFetch, backoffSequence, backoffWait, bash, buildAuthConfig, buildEndpointWithQuery, buildQueryString, calculateBackoff, calculateCost, calculateEntrySize, calculateImageCost, calculateSTTCost, calculateTTSCost, calculateVideoCost, canTaskExecute, createAgentStorage, createAuthenticatedFetch, createBashTool, createConnectorFromTemplate, createCreatePRTool, createCustomToolDelete, createCustomToolDraft, createCustomToolList, createCustomToolLoad, createCustomToolMetaTools, createCustomToolSave, createCustomToolTest, createDesktopGetCursorTool, createDesktopGetScreenSizeTool, createDesktopKeyboardKeyTool, createDesktopKeyboardTypeTool, createDesktopMouseClickTool, createDesktopMouseDragTool, createDesktopMouseMoveTool, createDesktopMouseScrollTool, createDesktopScreenshotTool, createDesktopWindowFocusTool, createDesktopWindowListTool, createDraftEmailTool, createEditFileTool, createEditMeetingTool, createEstimator, createExecuteJavaScriptTool, createFileAgentDefinitionStorage, createFileContextStorage, createFileCustomToolStorage, createFileMediaStorage, createFileRoutineDefinitionStorage, createFindMeetingSlotsTool, createGetMeetingTranscriptTool, createGetPRTool, createGitHubReadFileTool, createGlobTool, createGrepTool, createImageGenerationTool, createImageProvider, createListDirectoryTool, createMeetingTool, createMessageWithImages, createMetricsCollector, createPRCommentsTool, createPRFilesTool, createPlan, createProvider, createReadFileTool, createRoutineDefinition, createRoutineExecution, createSearchCodeTool, createSearchFilesTool, createSendEmailTool, createSpeechToTextTool, createTask, createTextMessage, createTextToSpeechTool, createVideoProvider, createVideoTools, createWriteFileTool, customToolDelete, customToolDraft, customToolList, customToolLoad, customToolSave, customToolTest, defaultDescribeCall, desktopGetCursor, desktopGetScreenSize, desktopKeyboardKey, desktopKeyboardType, desktopMouseClick, desktopMouseDrag, desktopMouseMove, desktopMouseScroll, desktopScreenshot, desktopTools, desktopWindowFocus, desktopWindowList, detectDependencyCycle, detectServiceFromURL, developerTools, documentToContent, editFile, evaluateCondition, executeRoutine, extractJSON, extractJSONField, extractNumber, findConnectorByServiceTypes, forPlan, forTasks, formatAttendees, formatPluginDisplayName, formatRecipients, generateEncryptionKey, generateSimplePlan, generateWebAPITool, getActiveImageModels, getActiveModels, getActiveSTTModels, getActiveTTSModels, getActiveVideoModels, getAllBuiltInTools, getAllServiceIds, getAllVendorLogos, getAllVendorTemplates, getBackgroundOutput, getConnectorTools, getCredentialsSetupURL, getDesktopDriver, getDocsURL, getImageModelInfo, getImageModelsByVendor, getImageModelsWithFeature, getMediaOutputHandler, getMediaStorage, getModelInfo, getModelsByVendor, getNextExecutableTasks, getRegisteredScrapeProviders, getRoutineProgress, getSTTModelInfo, getSTTModelsByVendor, getSTTModelsWithFeature, getServiceDefinition, getServiceInfo, getServicesByCategory, getTTSModelInfo, getTTSModelsByVendor, getTTSModelsWithFeature, getTaskDependencies, getToolByName, getToolCallDescription, getToolCategories, getToolRegistry, getToolsByCategory, getToolsRequiringConnector, getUserPathPrefix, getVendorAuthTemplate, getVendorColor, getVendorDefaultBaseURL, getVendorInfo, getVendorLogo, getVendorLogoCdnUrl, getVendorLogoSvg, getVendorTemplate, getVideoModelInfo, getVideoModelsByVendor, getVideoModelsWithAudio, getVideoModelsWithFeature, glob, globalErrorHandler, grep, hasClipboardImage, hasVendorLogo, hydrateCustomTool, isBlockedCommand, isErrorEvent, isExcludedExtension, isKnownService, isOutputTextDelta, isReasoningDelta, isReasoningDone, isResponseComplete, isSimpleScope, isStreamEvent, isTaskAwareScope, isTaskBlocked, isTeamsMeetingUrl, isTerminalMemoryStatus, isTerminalStatus, isToolCallArgumentsDelta, isToolCallArgumentsDone, isToolCallStart, isVendor, killBackgroundProcess, listConnectorsByServiceTypes, listDirectory, listVendorIds, listVendors, listVendorsByAuthType, listVendorsByCategory, listVendorsWithLogos, logger, mergeTextPieces, metrics, microsoftFetch, normalizeEmails, parseKeyCombo, parseRepository, readClipboardImage, readDocumentAsContent, readFile5 as readFile, registerScrapeProvider, resetDefaultDriver, resolveConnector, resolveDependencies, resolveMaxContextTokens, resolveMeetingId, resolveModelCapabilities, resolveRepository, resolveTemplates, retryWithBackoff, sanitizeToolName, scopeEquals, scopeMatches, setMediaOutputHandler, setMediaStorage, setMetricsCollector, simpleTokenEstimator, toConnectorOptions, toolRegistry, tools_exports as tools, updateTaskStatus, validatePath, writeFile5 as writeFile };
54466
+ // src/infrastructure/scheduling/SimpleScheduler.ts
54467
+ var SimpleScheduler = class {
54468
+ timers = /* @__PURE__ */ new Map();
54469
+ _isDestroyed = false;
54470
+ schedule(id, spec, callback) {
54471
+ if (this._isDestroyed) throw new Error("Scheduler has been destroyed");
54472
+ if (spec.cron) {
54473
+ throw new Error(
54474
+ `SimpleScheduler does not support cron expressions. Use a cron-capable scheduler implementation (e.g. node-cron, croner) or convert to intervalMs.`
54475
+ );
54476
+ }
54477
+ if (this.timers.has(id)) {
54478
+ this.cancel(id);
54479
+ }
54480
+ if (spec.intervalMs != null) {
54481
+ const timer = setInterval(() => {
54482
+ try {
54483
+ const result = callback();
54484
+ if (result && typeof result.catch === "function") {
54485
+ result.catch(() => {
54486
+ });
54487
+ }
54488
+ } catch {
54489
+ }
54490
+ }, spec.intervalMs);
54491
+ this.timers.set(id, { timer, type: "interval" });
54492
+ } else if (spec.once != null) {
54493
+ const delay = Math.max(0, spec.once - Date.now());
54494
+ const timer = setTimeout(() => {
54495
+ this.timers.delete(id);
54496
+ try {
54497
+ const result = callback();
54498
+ if (result && typeof result.catch === "function") {
54499
+ result.catch(() => {
54500
+ });
54501
+ }
54502
+ } catch {
54503
+ }
54504
+ }, delay);
54505
+ this.timers.set(id, { timer, type: "timeout" });
54506
+ } else {
54507
+ throw new Error("ScheduleSpec must have at least one of: cron, intervalMs, once");
54508
+ }
54509
+ return {
54510
+ id,
54511
+ cancel: () => this.cancel(id)
54512
+ };
54513
+ }
54514
+ cancel(id) {
54515
+ const entry = this.timers.get(id);
54516
+ if (!entry) return;
54517
+ if (entry.type === "interval") {
54518
+ clearInterval(entry.timer);
54519
+ } else {
54520
+ clearTimeout(entry.timer);
54521
+ }
54522
+ this.timers.delete(id);
54523
+ }
54524
+ cancelAll() {
54525
+ for (const [id] of this.timers) {
54526
+ this.cancel(id);
54527
+ }
54528
+ }
54529
+ has(id) {
54530
+ return this.timers.has(id);
54531
+ }
54532
+ destroy() {
54533
+ if (this._isDestroyed) return;
54534
+ this.cancelAll();
54535
+ this._isDestroyed = true;
54536
+ }
54537
+ get isDestroyed() {
54538
+ return this._isDestroyed;
54539
+ }
54540
+ };
54541
+
54542
+ // src/infrastructure/triggers/EventEmitterTrigger.ts
54543
+ var EventEmitterTrigger = class {
54544
+ listeners = /* @__PURE__ */ new Map();
54545
+ _isDestroyed = false;
54546
+ /**
54547
+ * Register a listener for an event. Returns an unsubscribe function.
54548
+ */
54549
+ on(event, callback) {
54550
+ if (this._isDestroyed) throw new Error("EventEmitterTrigger has been destroyed");
54551
+ if (!this.listeners.has(event)) {
54552
+ this.listeners.set(event, /* @__PURE__ */ new Set());
54553
+ }
54554
+ this.listeners.get(event).add(callback);
54555
+ return () => {
54556
+ this.listeners.get(event)?.delete(callback);
54557
+ };
54558
+ }
54559
+ /**
54560
+ * Emit an event to all registered listeners.
54561
+ */
54562
+ emit(event, payload) {
54563
+ if (this._isDestroyed) return;
54564
+ const callbacks = this.listeners.get(event);
54565
+ if (!callbacks) return;
54566
+ for (const cb of callbacks) {
54567
+ try {
54568
+ const result = cb(payload);
54569
+ if (result && typeof result.catch === "function") {
54570
+ result.catch(() => {
54571
+ });
54572
+ }
54573
+ } catch {
54574
+ }
54575
+ }
54576
+ }
54577
+ destroy() {
54578
+ if (this._isDestroyed) return;
54579
+ this.listeners.clear();
54580
+ this._isDestroyed = true;
54581
+ }
54582
+ get isDestroyed() {
54583
+ return this._isDestroyed;
54584
+ }
54585
+ };
54586
+
54587
+ export { AGENT_DEFINITION_FORMAT_VERSION, AIError, APPROVAL_STATE_VERSION, Agent, AgentContextNextGen, ApproximateTokenEstimator, BaseMediaProvider, BasePluginNextGen, BaseProvider, BaseTextProvider, BraveProvider, CONNECTOR_CONFIG_VERSION, CONTEXT_SESSION_FORMAT_VERSION, CUSTOM_TOOL_DEFINITION_VERSION, CheckpointManager, CircuitBreaker, CircuitOpenError, Connector, ConnectorConfigStore, ConnectorTools, ConsoleMetrics, ContentType, ContextOverflowError, DEFAULT_ALLOWLIST, DEFAULT_BACKOFF_CONFIG, DEFAULT_BASE_DELAY_MS, DEFAULT_CHECKPOINT_STRATEGY, DEFAULT_CIRCUIT_BREAKER_CONFIG, DEFAULT_CONFIG2 as DEFAULT_CONFIG, DEFAULT_CONNECTOR_TIMEOUT, DEFAULT_CONTEXT_CONFIG, DEFAULT_DESKTOP_CONFIG, DEFAULT_FEATURES, DEFAULT_FILESYSTEM_CONFIG, DEFAULT_HISTORY_MANAGER_CONFIG, DEFAULT_MAX_DELAY_MS, DEFAULT_MAX_RETRIES, DEFAULT_MEMORY_CONFIG, DEFAULT_PERMISSION_CONFIG, DEFAULT_RATE_LIMITER_CONFIG, DEFAULT_RETRYABLE_STATUSES, DEFAULT_SHELL_CONFIG, DESKTOP_TOOL_NAMES, DefaultCompactionStrategy, DependencyCycleError, DocumentReader, ErrorHandler, EventEmitterTrigger, ExecutionContext, ExternalDependencyHandler, FileAgentDefinitionStorage, FileConnectorStorage, FileContextStorage, FileCustomToolStorage, FileMediaStorage as FileMediaOutputHandler, FileMediaStorage, FilePersistentInstructionsStorage, FileRoutineDefinitionStorage, FileStorage, FileUserInfoStorage, FormatDetector, FrameworkLogger, HookManager, IMAGE_MODELS, IMAGE_MODEL_REGISTRY, ImageGeneration, InContextMemoryPluginNextGen, InMemoryAgentStateStorage, InMemoryHistoryStorage, InMemoryMetrics, InMemoryPlanStorage, InMemoryStorage, InvalidConfigError, InvalidToolArgumentsError, LLM_MODELS, LoggingPlugin, MCPClient, MCPConnectionError, MCPError, MCPProtocolError, MCPRegistry, MCPResourceError, MCPTimeoutError, MCPToolError, MEMORY_PRIORITY_VALUES, MODEL_REGISTRY, MemoryConnectorStorage, MemoryEvictionCompactor, MemoryStorage, MessageBuilder, MessageRole, ModelNotSupportedError, NoOpMetrics, NutTreeDriver, OAuthManager, ParallelTasksError, PersistentInstructionsPluginNextGen, PlanningAgent, ProviderAuthError, ProviderConfigAgent, ProviderContextLengthError, ProviderError, ProviderErrorMapper, ProviderNotFoundError, ProviderRateLimitError, ROUTINE_KEYS, RapidAPIProvider, RateLimitError, SERVICE_DEFINITIONS, SERVICE_INFO, SERVICE_URL_PATTERNS, SIMPLE_ICONS_CDN, STT_MODELS, STT_MODEL_REGISTRY, ScopedConnectorRegistry, ScrapeProvider, SearchProvider, SerperProvider, Services, SimpleScheduler, SpeechToText, StorageRegistry, StrategyRegistry, StreamEventType, StreamHelpers, StreamState, SummarizeCompactor, TERMINAL_TASK_STATUSES, TTS_MODELS, TTS_MODEL_REGISTRY, TaskTimeoutError, TaskValidationError, TavilyProvider, TextToSpeech, TokenBucketRateLimiter, ToolCallState, ToolCatalogPluginNextGen, ToolCatalogRegistry, ToolExecutionError, ToolExecutionPipeline, ToolManager, ToolNotFoundError, ToolPermissionManager, ToolRegistry, ToolTimeoutError, TruncateCompactor, UserInfoPluginNextGen, VENDORS, VENDOR_ICON_MAP, VIDEO_MODELS, VIDEO_MODEL_REGISTRY, Vendor, VideoGeneration, WorkingMemory, WorkingMemoryPluginNextGen, addJitter, allVendorTemplates, assertNotDestroyed, authenticatedFetch, backoffSequence, backoffWait, bash, buildAuthConfig, buildEndpointWithQuery, buildQueryString, calculateBackoff, calculateCost, calculateEntrySize, calculateImageCost, calculateSTTCost, calculateTTSCost, calculateVideoCost, canTaskExecute, createAgentStorage, createAuthenticatedFetch, createBashTool, createConnectorFromTemplate, createCreatePRTool, createCustomToolDelete, createCustomToolDraft, createCustomToolList, createCustomToolLoad, createCustomToolMetaTools, createCustomToolSave, createCustomToolTest, createDesktopGetCursorTool, createDesktopGetScreenSizeTool, createDesktopKeyboardKeyTool, createDesktopKeyboardTypeTool, createDesktopMouseClickTool, createDesktopMouseDragTool, createDesktopMouseMoveTool, createDesktopMouseScrollTool, createDesktopScreenshotTool, createDesktopWindowFocusTool, createDesktopWindowListTool, createDraftEmailTool, createEditFileTool, createEditMeetingTool, createEstimator, createExecuteJavaScriptTool, createExecutionRecorder, createFileAgentDefinitionStorage, createFileContextStorage, createFileCustomToolStorage, createFileMediaStorage, createFileRoutineDefinitionStorage, createFindMeetingSlotsTool, createGetMeetingTranscriptTool, createGetPRTool, createGitHubReadFileTool, createGlobTool, createGrepTool, createImageGenerationTool, createImageProvider, createListDirectoryTool, createMeetingTool, createMessageWithImages, createMetricsCollector, createMicrosoftListFilesTool, createMicrosoftReadFileTool, createMicrosoftSearchFilesTool, createPRCommentsTool, createPRFilesTool, createPlan, createProvider, createReadFileTool, createRoutineDefinition, createRoutineExecution, createRoutineExecutionRecord, createSearchCodeTool, createSearchFilesTool, createSendEmailTool, createSpeechToTextTool, createTask, createTaskSnapshots, createTextMessage, createTextToSpeechTool, createVideoProvider, createVideoTools, createWriteFileTool, customToolDelete, customToolDraft, customToolList, customToolLoad, customToolSave, customToolTest, defaultDescribeCall, desktopGetCursor, desktopGetScreenSize, desktopKeyboardKey, desktopKeyboardType, desktopMouseClick, desktopMouseDrag, desktopMouseMove, desktopMouseScroll, desktopScreenshot, desktopTools, desktopWindowFocus, desktopWindowList, detectDependencyCycle, detectServiceFromURL, developerTools, documentToContent, editFile, encodeSharingUrl, evaluateCondition, executeRoutine, extractJSON, extractJSONField, extractNumber, findConnectorByServiceTypes, forPlan, forTasks, formatAttendees, formatFileSize, formatPluginDisplayName, formatRecipients, generateEncryptionKey, generateSimplePlan, generateWebAPITool, getActiveImageModels, getActiveModels, getActiveSTTModels, getActiveTTSModels, getActiveVideoModels, getAllBuiltInTools, getAllServiceIds, getAllVendorLogos, getAllVendorTemplates, getBackgroundOutput, getConnectorTools, getCredentialsSetupURL, getDesktopDriver, getDocsURL, getDrivePrefix, getImageModelInfo, getImageModelsByVendor, getImageModelsWithFeature, getMediaOutputHandler, getMediaStorage, getModelInfo, getModelsByVendor, getNextExecutableTasks, getRegisteredScrapeProviders, getRoutineProgress, getSTTModelInfo, getSTTModelsByVendor, getSTTModelsWithFeature, getServiceDefinition, getServiceInfo, getServicesByCategory, getTTSModelInfo, getTTSModelsByVendor, getTTSModelsWithFeature, getTaskDependencies, getToolByName, getToolCallDescription, getToolCategories, getToolRegistry, getToolsByCategory, getToolsRequiringConnector, getUserPathPrefix, getVendorAuthTemplate, getVendorColor, getVendorDefaultBaseURL, getVendorInfo, getVendorLogo, getVendorLogoCdnUrl, getVendorLogoSvg, getVendorTemplate, getVideoModelInfo, getVideoModelsByVendor, getVideoModelsWithAudio, getVideoModelsWithFeature, glob, globalErrorHandler, grep, hasClipboardImage, hasVendorLogo, hydrateCustomTool, isBlockedCommand, isErrorEvent, isExcludedExtension, isKnownService, isMicrosoftFileUrl, isOutputTextDelta, isReasoningDelta, isReasoningDone, isResponseComplete, isSimpleScope, isStreamEvent, isTaskAwareScope, isTaskBlocked, isTeamsMeetingUrl, isTerminalMemoryStatus, isTerminalStatus, isToolCallArgumentsDelta, isToolCallArgumentsDone, isToolCallStart, isVendor, isWebUrl, killBackgroundProcess, listConnectorsByServiceTypes, listDirectory, listVendorIds, listVendors, listVendorsByAuthType, listVendorsByCategory, listVendorsWithLogos, logger, mergeTextPieces, metrics, microsoftFetch, normalizeEmails, parseKeyCombo, parseRepository, readClipboardImage, readDocumentAsContent, readFile5 as readFile, registerScrapeProvider, resetDefaultDriver, resolveConnector, resolveDependencies, resolveFileEndpoints, resolveFlowSource, resolveMaxContextTokens, resolveMeetingId, resolveModelCapabilities, resolveRepository, resolveTemplates, retryWithBackoff, sanitizeToolName, scopeEquals, scopeMatches, setMediaOutputHandler, setMediaStorage, setMetricsCollector, simpleTokenEstimator, toConnectorOptions, toolRegistry, tools_exports as tools, updateTaskStatus, validatePath, writeFile5 as writeFile };
51761
54588
  //# sourceMappingURL=index.js.map
51762
54589
  //# sourceMappingURL=index.js.map