@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/README.md +113 -13
- package/dist/capabilities/agents/index.d.cts +1 -1
- package/dist/capabilities/agents/index.d.ts +1 -1
- package/dist/capabilities/images/index.cjs.map +1 -1
- package/dist/capabilities/images/index.js.map +1 -1
- package/dist/{index-CzGnmqOs.d.ts → index-C6ApwIzB.d.ts} +323 -1
- package/dist/{index-CEjKTeSb.d.cts → index-CsQOVhqe.d.cts} +323 -1
- package/dist/index.cjs +2880 -36
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +568 -18
- package/dist/index.d.ts +568 -18
- package/dist/index.js +2864 -37
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -9992,15 +9992,16 @@ var init_PDFHandler = __esm({
|
|
|
9992
9992
|
const unpdf = await getUnpdf();
|
|
9993
9993
|
const pieces = [];
|
|
9994
9994
|
let pieceIndex = 0;
|
|
9995
|
+
const data = new Uint8Array(buffer);
|
|
9995
9996
|
let metadata = {};
|
|
9996
9997
|
const includeMetadata = options.formatOptions?.pdf?.includeMetadata !== false;
|
|
9997
9998
|
if (includeMetadata) {
|
|
9998
9999
|
try {
|
|
9999
|
-
metadata = await unpdf.getMeta(
|
|
10000
|
+
metadata = await unpdf.getMeta(data);
|
|
10000
10001
|
} catch {
|
|
10001
10002
|
}
|
|
10002
10003
|
}
|
|
10003
|
-
const textResult = await unpdf.extractText(
|
|
10004
|
+
const textResult = await unpdf.extractText(data, { mergePages: false });
|
|
10004
10005
|
const pages = textResult?.pages || textResult?.text ? Array.isArray(textResult.text) ? textResult.text : [textResult.text] : [];
|
|
10005
10006
|
const requestedPages = options.pages;
|
|
10006
10007
|
const pageEntries = pages.map((text, i) => ({ text, pageNum: i + 1 }));
|
|
@@ -10053,7 +10054,7 @@ var init_PDFHandler = __esm({
|
|
|
10053
10054
|
}
|
|
10054
10055
|
if (options.extractImages !== false) {
|
|
10055
10056
|
try {
|
|
10056
|
-
const imagesResult = await unpdf.extractImages(
|
|
10057
|
+
const imagesResult = await unpdf.extractImages(data, {});
|
|
10057
10058
|
const images = imagesResult?.images || [];
|
|
10058
10059
|
for (const img of images) {
|
|
10059
10060
|
if (!img.data) continue;
|
|
@@ -10286,6 +10287,475 @@ init_Connector();
|
|
|
10286
10287
|
init_ScopedConnectorRegistry();
|
|
10287
10288
|
init_StorageRegistry();
|
|
10288
10289
|
|
|
10290
|
+
// src/core/ToolCatalogRegistry.ts
|
|
10291
|
+
init_Logger();
|
|
10292
|
+
var ToolCatalogRegistry = class {
|
|
10293
|
+
/** Category definitions: name → definition */
|
|
10294
|
+
static _categories = /* @__PURE__ */ new Map();
|
|
10295
|
+
/** Tools per category: category name → tool entries */
|
|
10296
|
+
static _tools = /* @__PURE__ */ new Map();
|
|
10297
|
+
/** Whether built-in tools have been registered */
|
|
10298
|
+
static _initialized = false;
|
|
10299
|
+
/** Lazy-loaded ConnectorTools module. null = not attempted, false = failed */
|
|
10300
|
+
static _connectorToolsModule = null;
|
|
10301
|
+
// --- Built-in category descriptions ---
|
|
10302
|
+
static BUILTIN_DESCRIPTIONS = {
|
|
10303
|
+
filesystem: "Read, write, edit, search, and list files and directories",
|
|
10304
|
+
shell: "Execute bash/shell commands",
|
|
10305
|
+
web: "Fetch and process web content",
|
|
10306
|
+
code: "Execute JavaScript code in sandboxed VM",
|
|
10307
|
+
json: "Parse, query, and transform JSON data",
|
|
10308
|
+
desktop: "Screenshot, mouse, keyboard, and window desktop automation",
|
|
10309
|
+
"custom-tools": "Create, save, load, and test custom tool definitions",
|
|
10310
|
+
routines: "Generate and manage agent routines",
|
|
10311
|
+
other: "Miscellaneous tools"
|
|
10312
|
+
};
|
|
10313
|
+
// ========================================================================
|
|
10314
|
+
// Static Helpers (DRY)
|
|
10315
|
+
// ========================================================================
|
|
10316
|
+
/**
|
|
10317
|
+
* Convert a hyphenated or plain name to a display name.
|
|
10318
|
+
* E.g., 'custom-tools' → 'Custom Tools', 'filesystem' → 'Filesystem'
|
|
10319
|
+
*/
|
|
10320
|
+
static toDisplayName(name) {
|
|
10321
|
+
return name.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
10322
|
+
}
|
|
10323
|
+
/**
|
|
10324
|
+
* Parse a connector category name, returning the connector name or null.
|
|
10325
|
+
* E.g., 'connector:github' → 'github', 'filesystem' → null
|
|
10326
|
+
*/
|
|
10327
|
+
static parseConnectorCategory(category) {
|
|
10328
|
+
return category.startsWith("connector:") ? category.slice("connector:".length) : null;
|
|
10329
|
+
}
|
|
10330
|
+
/**
|
|
10331
|
+
* Get the ConnectorTools module (lazy-loaded, cached).
|
|
10332
|
+
* Returns null if ConnectorTools is not available.
|
|
10333
|
+
* Uses false sentinel to prevent retrying after first failure.
|
|
10334
|
+
*
|
|
10335
|
+
* NOTE: The dynamic require() path fails in bundled environments (Meteor, Webpack).
|
|
10336
|
+
* Call setConnectorToolsModule() at app startup to inject the module explicitly.
|
|
10337
|
+
*/
|
|
10338
|
+
static getConnectorToolsModule() {
|
|
10339
|
+
if (this._connectorToolsModule === null) {
|
|
10340
|
+
try {
|
|
10341
|
+
this._connectorToolsModule = __require("../../tools/connector/ConnectorTools.js");
|
|
10342
|
+
} catch {
|
|
10343
|
+
this._connectorToolsModule = false;
|
|
10344
|
+
}
|
|
10345
|
+
}
|
|
10346
|
+
return this._connectorToolsModule || null;
|
|
10347
|
+
}
|
|
10348
|
+
/**
|
|
10349
|
+
* Explicitly set the ConnectorTools module reference.
|
|
10350
|
+
*
|
|
10351
|
+
* Use this in bundled environments (Meteor, Webpack, etc.) where the lazy
|
|
10352
|
+
* require('../../tools/connector/ConnectorTools.js') fails due to path resolution.
|
|
10353
|
+
*
|
|
10354
|
+
* @example
|
|
10355
|
+
* ```typescript
|
|
10356
|
+
* import { ToolCatalogRegistry, ConnectorTools } from '@everworker/oneringai';
|
|
10357
|
+
* ToolCatalogRegistry.setConnectorToolsModule({ ConnectorTools });
|
|
10358
|
+
* ```
|
|
10359
|
+
*/
|
|
10360
|
+
static setConnectorToolsModule(mod) {
|
|
10361
|
+
this._connectorToolsModule = mod;
|
|
10362
|
+
}
|
|
10363
|
+
// ========================================================================
|
|
10364
|
+
// Registration
|
|
10365
|
+
// ========================================================================
|
|
10366
|
+
/**
|
|
10367
|
+
* Register a tool category.
|
|
10368
|
+
* If the category already exists, updates its metadata.
|
|
10369
|
+
* @throws Error if name is empty or whitespace
|
|
10370
|
+
*/
|
|
10371
|
+
static registerCategory(def) {
|
|
10372
|
+
if (!def.name || !def.name.trim()) {
|
|
10373
|
+
throw new Error("[ToolCatalogRegistry] Category name cannot be empty");
|
|
10374
|
+
}
|
|
10375
|
+
this._categories.set(def.name, def);
|
|
10376
|
+
if (!this._tools.has(def.name)) {
|
|
10377
|
+
this._tools.set(def.name, []);
|
|
10378
|
+
}
|
|
10379
|
+
}
|
|
10380
|
+
/**
|
|
10381
|
+
* Register multiple tools in a category.
|
|
10382
|
+
* The category is auto-created if it doesn't exist (with a generic description).
|
|
10383
|
+
* @throws Error if category name is empty or whitespace
|
|
10384
|
+
*/
|
|
10385
|
+
static registerTools(category, tools) {
|
|
10386
|
+
if (!category || !category.trim()) {
|
|
10387
|
+
throw new Error("[ToolCatalogRegistry] Category name cannot be empty");
|
|
10388
|
+
}
|
|
10389
|
+
if (!this._categories.has(category)) {
|
|
10390
|
+
this._categories.set(category, {
|
|
10391
|
+
name: category,
|
|
10392
|
+
displayName: this.toDisplayName(category),
|
|
10393
|
+
description: this.BUILTIN_DESCRIPTIONS[category] ?? `Tools in the ${category} category`
|
|
10394
|
+
});
|
|
10395
|
+
}
|
|
10396
|
+
const existing = this._tools.get(category) ?? [];
|
|
10397
|
+
const existingNames = new Set(existing.map((t) => t.name));
|
|
10398
|
+
const newTools = tools.filter((t) => !existingNames.has(t.name));
|
|
10399
|
+
const updated = existing.map((t) => {
|
|
10400
|
+
const replacement = tools.find((nt) => nt.name === t.name);
|
|
10401
|
+
return replacement ?? t;
|
|
10402
|
+
});
|
|
10403
|
+
this._tools.set(category, [...updated, ...newTools]);
|
|
10404
|
+
}
|
|
10405
|
+
/**
|
|
10406
|
+
* Register a single tool in a category.
|
|
10407
|
+
*/
|
|
10408
|
+
static registerTool(category, tool) {
|
|
10409
|
+
this.registerTools(category, [tool]);
|
|
10410
|
+
}
|
|
10411
|
+
/**
|
|
10412
|
+
* Unregister a category and all its tools.
|
|
10413
|
+
*/
|
|
10414
|
+
static unregisterCategory(category) {
|
|
10415
|
+
const hadCategory = this._categories.delete(category);
|
|
10416
|
+
this._tools.delete(category);
|
|
10417
|
+
return hadCategory;
|
|
10418
|
+
}
|
|
10419
|
+
/**
|
|
10420
|
+
* Unregister a single tool from a category.
|
|
10421
|
+
*/
|
|
10422
|
+
static unregisterTool(category, toolName) {
|
|
10423
|
+
const tools = this._tools.get(category);
|
|
10424
|
+
if (!tools) return false;
|
|
10425
|
+
const idx = tools.findIndex((t) => t.name === toolName);
|
|
10426
|
+
if (idx === -1) return false;
|
|
10427
|
+
tools.splice(idx, 1);
|
|
10428
|
+
return true;
|
|
10429
|
+
}
|
|
10430
|
+
// ========================================================================
|
|
10431
|
+
// Query
|
|
10432
|
+
// ========================================================================
|
|
10433
|
+
/**
|
|
10434
|
+
* Get all registered categories.
|
|
10435
|
+
*/
|
|
10436
|
+
static getCategories() {
|
|
10437
|
+
this.ensureInitialized();
|
|
10438
|
+
return Array.from(this._categories.values());
|
|
10439
|
+
}
|
|
10440
|
+
/**
|
|
10441
|
+
* Get a single category by name.
|
|
10442
|
+
*/
|
|
10443
|
+
static getCategory(name) {
|
|
10444
|
+
this.ensureInitialized();
|
|
10445
|
+
return this._categories.get(name);
|
|
10446
|
+
}
|
|
10447
|
+
/**
|
|
10448
|
+
* Check if a category exists.
|
|
10449
|
+
*/
|
|
10450
|
+
static hasCategory(name) {
|
|
10451
|
+
this.ensureInitialized();
|
|
10452
|
+
return this._categories.has(name);
|
|
10453
|
+
}
|
|
10454
|
+
/**
|
|
10455
|
+
* Get all tools in a category.
|
|
10456
|
+
*/
|
|
10457
|
+
static getToolsInCategory(category) {
|
|
10458
|
+
this.ensureInitialized();
|
|
10459
|
+
return this._tools.get(category) ?? [];
|
|
10460
|
+
}
|
|
10461
|
+
/**
|
|
10462
|
+
* Get all catalog tools across all categories.
|
|
10463
|
+
*/
|
|
10464
|
+
static getAllCatalogTools() {
|
|
10465
|
+
this.ensureInitialized();
|
|
10466
|
+
const all = [];
|
|
10467
|
+
for (const tools of this._tools.values()) {
|
|
10468
|
+
all.push(...tools);
|
|
10469
|
+
}
|
|
10470
|
+
return all;
|
|
10471
|
+
}
|
|
10472
|
+
/**
|
|
10473
|
+
* Find a tool by name across all categories.
|
|
10474
|
+
*/
|
|
10475
|
+
static findTool(name) {
|
|
10476
|
+
this.ensureInitialized();
|
|
10477
|
+
for (const [category, tools] of this._tools) {
|
|
10478
|
+
const entry = tools.find((t) => t.name === name);
|
|
10479
|
+
if (entry) return { category, entry };
|
|
10480
|
+
}
|
|
10481
|
+
return void 0;
|
|
10482
|
+
}
|
|
10483
|
+
// ========================================================================
|
|
10484
|
+
// Filtering
|
|
10485
|
+
// ========================================================================
|
|
10486
|
+
/**
|
|
10487
|
+
* Filter categories by scope.
|
|
10488
|
+
*/
|
|
10489
|
+
static filterCategories(scope) {
|
|
10490
|
+
const all = this.getCategories();
|
|
10491
|
+
if (!scope) return all;
|
|
10492
|
+
return all.filter((cat) => this.isCategoryAllowed(cat.name, scope));
|
|
10493
|
+
}
|
|
10494
|
+
/**
|
|
10495
|
+
* Check if a category is allowed by a scope.
|
|
10496
|
+
*/
|
|
10497
|
+
static isCategoryAllowed(name, scope) {
|
|
10498
|
+
if (!scope) return true;
|
|
10499
|
+
if (Array.isArray(scope)) {
|
|
10500
|
+
return scope.includes(name);
|
|
10501
|
+
}
|
|
10502
|
+
if ("include" in scope) {
|
|
10503
|
+
return scope.include.includes(name);
|
|
10504
|
+
}
|
|
10505
|
+
if ("exclude" in scope) {
|
|
10506
|
+
return !scope.exclude.includes(name);
|
|
10507
|
+
}
|
|
10508
|
+
return true;
|
|
10509
|
+
}
|
|
10510
|
+
// ========================================================================
|
|
10511
|
+
// Connector Discovery
|
|
10512
|
+
// ========================================================================
|
|
10513
|
+
/**
|
|
10514
|
+
* Discover all connector categories with their tools.
|
|
10515
|
+
* Calls ConnectorTools.discoverAll() and filters by scope/identities.
|
|
10516
|
+
*
|
|
10517
|
+
* @param options - Optional filtering
|
|
10518
|
+
* @returns Array of connector category info
|
|
10519
|
+
*/
|
|
10520
|
+
static discoverConnectorCategories(options) {
|
|
10521
|
+
const mod = this.getConnectorToolsModule();
|
|
10522
|
+
if (!mod) return [];
|
|
10523
|
+
try {
|
|
10524
|
+
const discovered = mod.ConnectorTools.discoverAll();
|
|
10525
|
+
const results = [];
|
|
10526
|
+
for (const [connectorName, tools] of discovered) {
|
|
10527
|
+
const catName = `connector:${connectorName}`;
|
|
10528
|
+
if (options?.scope && !this.isCategoryAllowed(catName, options.scope)) {
|
|
10529
|
+
continue;
|
|
10530
|
+
}
|
|
10531
|
+
if (options?.identities?.length) {
|
|
10532
|
+
const hasIdentity = options.identities.some((id) => id.connector === connectorName);
|
|
10533
|
+
if (!hasIdentity) continue;
|
|
10534
|
+
}
|
|
10535
|
+
const preRegistered = this.getCategory(catName);
|
|
10536
|
+
results.push({
|
|
10537
|
+
name: catName,
|
|
10538
|
+
displayName: preRegistered?.displayName ?? this.toDisplayName(connectorName),
|
|
10539
|
+
description: preRegistered?.description ?? `API tools for ${connectorName}`,
|
|
10540
|
+
toolCount: tools.length,
|
|
10541
|
+
tools
|
|
10542
|
+
});
|
|
10543
|
+
}
|
|
10544
|
+
return results;
|
|
10545
|
+
} catch {
|
|
10546
|
+
return [];
|
|
10547
|
+
}
|
|
10548
|
+
}
|
|
10549
|
+
/**
|
|
10550
|
+
* Resolve tools for a specific connector category.
|
|
10551
|
+
*
|
|
10552
|
+
* @param category - Category name in 'connector:<name>' format
|
|
10553
|
+
* @returns Array of resolved tools with names
|
|
10554
|
+
*/
|
|
10555
|
+
static resolveConnectorCategoryTools(category) {
|
|
10556
|
+
const connectorName = this.parseConnectorCategory(category);
|
|
10557
|
+
if (!connectorName) return [];
|
|
10558
|
+
const mod = this.getConnectorToolsModule();
|
|
10559
|
+
if (!mod) return [];
|
|
10560
|
+
try {
|
|
10561
|
+
const tools = mod.ConnectorTools.for(connectorName);
|
|
10562
|
+
return tools.map((t) => ({ tool: t, name: t.definition.function.name }));
|
|
10563
|
+
} catch {
|
|
10564
|
+
return [];
|
|
10565
|
+
}
|
|
10566
|
+
}
|
|
10567
|
+
// ========================================================================
|
|
10568
|
+
// Resolution
|
|
10569
|
+
// ========================================================================
|
|
10570
|
+
/**
|
|
10571
|
+
* Resolve tool names to ToolFunction[].
|
|
10572
|
+
*
|
|
10573
|
+
* Searches registered categories and (optionally) connector tools.
|
|
10574
|
+
* Used by app-level executors (e.g., V25's OneRingAgentExecutor).
|
|
10575
|
+
*
|
|
10576
|
+
* @param toolNames - Array of tool names to resolve
|
|
10577
|
+
* @param options - Resolution options
|
|
10578
|
+
* @returns Resolved tool functions (skips unresolvable names with warning)
|
|
10579
|
+
*/
|
|
10580
|
+
static resolveTools(toolNames, options) {
|
|
10581
|
+
this.ensureInitialized();
|
|
10582
|
+
const resolved = [];
|
|
10583
|
+
const missing = [];
|
|
10584
|
+
for (const name of toolNames) {
|
|
10585
|
+
const found = this.findTool(name);
|
|
10586
|
+
if (found) {
|
|
10587
|
+
const tool = this.resolveEntryTool(found.entry, options?.context);
|
|
10588
|
+
if (tool) {
|
|
10589
|
+
resolved.push(tool);
|
|
10590
|
+
continue;
|
|
10591
|
+
}
|
|
10592
|
+
}
|
|
10593
|
+
if (options?.includeConnectors) {
|
|
10594
|
+
const connectorTool = this.findConnectorTool(name);
|
|
10595
|
+
if (connectorTool) {
|
|
10596
|
+
resolved.push(connectorTool);
|
|
10597
|
+
continue;
|
|
10598
|
+
}
|
|
10599
|
+
}
|
|
10600
|
+
missing.push(name);
|
|
10601
|
+
}
|
|
10602
|
+
if (missing.length > 0) {
|
|
10603
|
+
exports.logger.warn(
|
|
10604
|
+
{ missing, resolved: resolved.length, total: toolNames.length },
|
|
10605
|
+
`[ToolCatalogRegistry.resolveTools] Could not resolve ${missing.length} tool(s): ${missing.join(", ")}`
|
|
10606
|
+
);
|
|
10607
|
+
}
|
|
10608
|
+
return resolved;
|
|
10609
|
+
}
|
|
10610
|
+
/**
|
|
10611
|
+
* Resolve tools grouped by connector name.
|
|
10612
|
+
*
|
|
10613
|
+
* Tools with a `connectorName` go into `byConnector`; all others go into `plain`.
|
|
10614
|
+
* Supports factory-based tool creation via `createTool` when context is provided.
|
|
10615
|
+
*
|
|
10616
|
+
* @param toolNames - Array of tool names to resolve
|
|
10617
|
+
* @param context - Optional context passed to createTool factories
|
|
10618
|
+
* @param options - Resolution options
|
|
10619
|
+
* @returns Grouped tools: plain + byConnector map
|
|
10620
|
+
*/
|
|
10621
|
+
static resolveToolsGrouped(toolNames, context, options) {
|
|
10622
|
+
this.ensureInitialized();
|
|
10623
|
+
const plain = [];
|
|
10624
|
+
const byConnector = /* @__PURE__ */ new Map();
|
|
10625
|
+
for (const name of toolNames) {
|
|
10626
|
+
const found = this.findTool(name);
|
|
10627
|
+
if (found) {
|
|
10628
|
+
const entry = found.entry;
|
|
10629
|
+
const tool = this.resolveEntryTool(entry, context);
|
|
10630
|
+
if (!tool) continue;
|
|
10631
|
+
if (entry.connectorName) {
|
|
10632
|
+
const list = byConnector.get(entry.connectorName) ?? [];
|
|
10633
|
+
list.push(tool);
|
|
10634
|
+
byConnector.set(entry.connectorName, list);
|
|
10635
|
+
} else {
|
|
10636
|
+
plain.push(tool);
|
|
10637
|
+
}
|
|
10638
|
+
continue;
|
|
10639
|
+
}
|
|
10640
|
+
if (options?.includeConnectors) {
|
|
10641
|
+
const connectorTool = this.findConnectorTool(name);
|
|
10642
|
+
if (connectorTool) {
|
|
10643
|
+
plain.push(connectorTool);
|
|
10644
|
+
continue;
|
|
10645
|
+
}
|
|
10646
|
+
}
|
|
10647
|
+
}
|
|
10648
|
+
return { plain, byConnector };
|
|
10649
|
+
}
|
|
10650
|
+
/**
|
|
10651
|
+
* Resolve a tool from a CatalogToolEntry, using factory if available.
|
|
10652
|
+
* Returns null if neither tool nor createTool is available.
|
|
10653
|
+
*/
|
|
10654
|
+
static resolveEntryTool(entry, context) {
|
|
10655
|
+
if (entry.createTool && context) {
|
|
10656
|
+
try {
|
|
10657
|
+
return entry.createTool(context);
|
|
10658
|
+
} catch (e) {
|
|
10659
|
+
exports.logger.warn(`[ToolCatalogRegistry] Factory failed for '${entry.name}': ${e}`);
|
|
10660
|
+
return null;
|
|
10661
|
+
}
|
|
10662
|
+
}
|
|
10663
|
+
return entry.tool ?? null;
|
|
10664
|
+
}
|
|
10665
|
+
/**
|
|
10666
|
+
* Search connector tools by name (uses lazy accessor).
|
|
10667
|
+
*/
|
|
10668
|
+
static findConnectorTool(name) {
|
|
10669
|
+
const mod = this.getConnectorToolsModule();
|
|
10670
|
+
if (!mod) return void 0;
|
|
10671
|
+
try {
|
|
10672
|
+
const allConnectorTools = mod.ConnectorTools.discoverAll();
|
|
10673
|
+
for (const [, tools] of allConnectorTools) {
|
|
10674
|
+
for (const tool of tools) {
|
|
10675
|
+
if (tool.definition.function.name === name) {
|
|
10676
|
+
return tool;
|
|
10677
|
+
}
|
|
10678
|
+
}
|
|
10679
|
+
}
|
|
10680
|
+
} catch {
|
|
10681
|
+
}
|
|
10682
|
+
return void 0;
|
|
10683
|
+
}
|
|
10684
|
+
// ========================================================================
|
|
10685
|
+
// Built-in initialization
|
|
10686
|
+
// ========================================================================
|
|
10687
|
+
/**
|
|
10688
|
+
* Ensure built-in tools from registry.generated.ts are registered.
|
|
10689
|
+
* Called lazily on first query.
|
|
10690
|
+
*
|
|
10691
|
+
* In ESM environments, call `initializeFromRegistry(toolRegistry)` explicitly
|
|
10692
|
+
* from your app startup instead of relying on auto-initialization.
|
|
10693
|
+
*/
|
|
10694
|
+
static ensureInitialized() {
|
|
10695
|
+
if (this._initialized) return;
|
|
10696
|
+
this._initialized = true;
|
|
10697
|
+
try {
|
|
10698
|
+
const mod = __require("../../tools/registry.generated.js");
|
|
10699
|
+
const registry = mod?.toolRegistry ?? mod?.default?.toolRegistry;
|
|
10700
|
+
if (Array.isArray(registry)) {
|
|
10701
|
+
this.registerFromToolRegistry(registry);
|
|
10702
|
+
}
|
|
10703
|
+
} catch {
|
|
10704
|
+
}
|
|
10705
|
+
}
|
|
10706
|
+
/**
|
|
10707
|
+
* Explicitly initialize from the generated tool registry.
|
|
10708
|
+
* Call this at app startup in ESM environments where lazy require() doesn't work.
|
|
10709
|
+
*
|
|
10710
|
+
* @example
|
|
10711
|
+
* ```typescript
|
|
10712
|
+
* import { toolRegistry } from './tools/registry.generated.js';
|
|
10713
|
+
* ToolCatalogRegistry.initializeFromRegistry(toolRegistry);
|
|
10714
|
+
* ```
|
|
10715
|
+
*/
|
|
10716
|
+
static initializeFromRegistry(registry) {
|
|
10717
|
+
this._initialized = true;
|
|
10718
|
+
this.registerFromToolRegistry(registry);
|
|
10719
|
+
}
|
|
10720
|
+
/**
|
|
10721
|
+
* Internal: register tools from a tool registry array.
|
|
10722
|
+
*/
|
|
10723
|
+
static registerFromToolRegistry(registry) {
|
|
10724
|
+
for (const entry of registry) {
|
|
10725
|
+
const category = entry.category;
|
|
10726
|
+
if (!this._categories.has(category)) {
|
|
10727
|
+
this.registerCategory({
|
|
10728
|
+
name: category,
|
|
10729
|
+
displayName: this.toDisplayName(category),
|
|
10730
|
+
description: this.BUILTIN_DESCRIPTIONS[category] ?? `Built-in ${category} tools`
|
|
10731
|
+
});
|
|
10732
|
+
}
|
|
10733
|
+
const catalogEntry = {
|
|
10734
|
+
tool: entry.tool,
|
|
10735
|
+
name: entry.name,
|
|
10736
|
+
displayName: entry.displayName,
|
|
10737
|
+
description: entry.description,
|
|
10738
|
+
safeByDefault: entry.safeByDefault,
|
|
10739
|
+
requiresConnector: entry.requiresConnector
|
|
10740
|
+
};
|
|
10741
|
+
const existing = this._tools.get(category) ?? [];
|
|
10742
|
+
if (!existing.some((t) => t.name === catalogEntry.name)) {
|
|
10743
|
+
existing.push(catalogEntry);
|
|
10744
|
+
this._tools.set(category, existing);
|
|
10745
|
+
}
|
|
10746
|
+
}
|
|
10747
|
+
}
|
|
10748
|
+
/**
|
|
10749
|
+
* Reset the registry. Primarily for testing.
|
|
10750
|
+
*/
|
|
10751
|
+
static reset() {
|
|
10752
|
+
this._categories.clear();
|
|
10753
|
+
this._tools.clear();
|
|
10754
|
+
this._initialized = false;
|
|
10755
|
+
this._connectorToolsModule = null;
|
|
10756
|
+
}
|
|
10757
|
+
};
|
|
10758
|
+
|
|
10289
10759
|
// src/core/BaseAgent.ts
|
|
10290
10760
|
init_Connector();
|
|
10291
10761
|
|
|
@@ -10323,6 +10793,14 @@ var DEFAULT_ALLOWLIST = [
|
|
|
10323
10793
|
"user_info_get",
|
|
10324
10794
|
"user_info_remove",
|
|
10325
10795
|
"user_info_clear",
|
|
10796
|
+
// TODO tools (user-specific data - safe)
|
|
10797
|
+
"todo_add",
|
|
10798
|
+
"todo_update",
|
|
10799
|
+
"todo_remove",
|
|
10800
|
+
// Tool catalog tools (browsing and loading — safe)
|
|
10801
|
+
"tool_catalog_search",
|
|
10802
|
+
"tool_catalog_load",
|
|
10803
|
+
"tool_catalog_unload",
|
|
10326
10804
|
// Meta-tools (internal coordination)
|
|
10327
10805
|
"_start_planning",
|
|
10328
10806
|
"_modify_plan",
|
|
@@ -11637,6 +12115,17 @@ var ToolManager = class extends eventemitter3.EventEmitter {
|
|
|
11637
12115
|
if (!toolNames) return [];
|
|
11638
12116
|
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);
|
|
11639
12117
|
}
|
|
12118
|
+
/**
|
|
12119
|
+
* Get all registered tool names in a category.
|
|
12120
|
+
* Used by ToolCatalogPlugin for bulk enable/disable.
|
|
12121
|
+
*/
|
|
12122
|
+
getByCategory(category) {
|
|
12123
|
+
const names = [];
|
|
12124
|
+
for (const [name, reg] of this.registry) {
|
|
12125
|
+
if (reg.category === category) names.push(name);
|
|
12126
|
+
}
|
|
12127
|
+
return names;
|
|
12128
|
+
}
|
|
11640
12129
|
/**
|
|
11641
12130
|
* Get tool registration info
|
|
11642
12131
|
*/
|
|
@@ -15802,7 +16291,35 @@ User info is automatically shown in context \u2014 no need to call user_info_get
|
|
|
15802
16291
|
|
|
15803
16292
|
**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.
|
|
15804
16293
|
|
|
15805
|
-
**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
|
|
16294
|
+
**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!
|
|
16295
|
+
|
|
16296
|
+
## TODO Management
|
|
16297
|
+
|
|
16298
|
+
TODOs are stored alongside user info and shown in a separate "Current TODOs" section in context.
|
|
16299
|
+
|
|
16300
|
+
**Tools:**
|
|
16301
|
+
- \`todo_add(title, description?, people?, dueDate?, tags?)\`: Create a new TODO item
|
|
16302
|
+
- \`todo_update(id, title?, description?, people?, dueDate?, tags?, status?)\`: Update an existing TODO
|
|
16303
|
+
- \`todo_remove(id)\`: Delete a TODO item
|
|
16304
|
+
|
|
16305
|
+
**Proactive creation \u2014 be helpful:**
|
|
16306
|
+
- If the user's message implies an action item, task, or deadline \u2192 ask "Would you like me to create a TODO for this?"
|
|
16307
|
+
- If the user explicitly says "remind me", "track this", "don't forget" \u2192 create a TODO immediately without asking.
|
|
16308
|
+
- When discussing plans with deadlines or deliverables \u2192 suggest relevant TODOs.
|
|
16309
|
+
- When the user mentions other people involved \u2192 include them in the \`people\` field.
|
|
16310
|
+
- Suggest appropriate tags based on context (e.g. "work", "personal", "urgent").
|
|
16311
|
+
|
|
16312
|
+
**Reminder rules:**
|
|
16313
|
+
- 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\`.
|
|
16314
|
+
- Do NOT remind again in the same day unless the user explicitly asks about their TODOs.
|
|
16315
|
+
- When reminding, prioritize: overdue items first, then items due today, then items due tomorrow.
|
|
16316
|
+
- If the user asks about their TODOs or schedule, always answer regardless of reminder status.
|
|
16317
|
+
- After completing a TODO, mark it as done via \`todo_update(id, status: 'done')\`. Suggest marking items done when context indicates completion.
|
|
16318
|
+
|
|
16319
|
+
**Cleanup rules:**
|
|
16320
|
+
- Completed TODOs older than 48 hours (check updatedAt of done items) \u2192 auto-delete via \`todo_remove\` without asking.
|
|
16321
|
+
- 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?"
|
|
16322
|
+
- Run cleanup checks at the same time as reminders (once per day, using \`_todo_last_reminded\` marker).`;
|
|
15806
16323
|
var userInfoSetDefinition = {
|
|
15807
16324
|
type: "function",
|
|
15808
16325
|
function: {
|
|
@@ -15879,6 +16396,102 @@ var userInfoClearDefinition = {
|
|
|
15879
16396
|
}
|
|
15880
16397
|
}
|
|
15881
16398
|
};
|
|
16399
|
+
var todoAddDefinition = {
|
|
16400
|
+
type: "function",
|
|
16401
|
+
function: {
|
|
16402
|
+
name: "todo_add",
|
|
16403
|
+
description: "Create a new TODO item for the user. Returns the generated todo ID.",
|
|
16404
|
+
parameters: {
|
|
16405
|
+
type: "object",
|
|
16406
|
+
properties: {
|
|
16407
|
+
title: {
|
|
16408
|
+
type: "string",
|
|
16409
|
+
description: "Short title for the TODO (required)"
|
|
16410
|
+
},
|
|
16411
|
+
description: {
|
|
16412
|
+
type: "string",
|
|
16413
|
+
description: "Optional detailed description"
|
|
16414
|
+
},
|
|
16415
|
+
people: {
|
|
16416
|
+
type: "array",
|
|
16417
|
+
items: { type: "string" },
|
|
16418
|
+
description: "People involved besides the current user (optional)"
|
|
16419
|
+
},
|
|
16420
|
+
dueDate: {
|
|
16421
|
+
type: "string",
|
|
16422
|
+
description: "Due date in ISO format YYYY-MM-DD (optional)"
|
|
16423
|
+
},
|
|
16424
|
+
tags: {
|
|
16425
|
+
type: "array",
|
|
16426
|
+
items: { type: "string" },
|
|
16427
|
+
description: 'Categorization tags (optional, e.g. "work", "personal", "urgent")'
|
|
16428
|
+
}
|
|
16429
|
+
},
|
|
16430
|
+
required: ["title"]
|
|
16431
|
+
}
|
|
16432
|
+
}
|
|
16433
|
+
};
|
|
16434
|
+
var todoUpdateDefinition = {
|
|
16435
|
+
type: "function",
|
|
16436
|
+
function: {
|
|
16437
|
+
name: "todo_update",
|
|
16438
|
+
description: "Update an existing TODO item. Only provided fields are changed.",
|
|
16439
|
+
parameters: {
|
|
16440
|
+
type: "object",
|
|
16441
|
+
properties: {
|
|
16442
|
+
id: {
|
|
16443
|
+
type: "string",
|
|
16444
|
+
description: 'The todo ID (e.g. "todo_a1b2c3")'
|
|
16445
|
+
},
|
|
16446
|
+
title: {
|
|
16447
|
+
type: "string",
|
|
16448
|
+
description: "New title"
|
|
16449
|
+
},
|
|
16450
|
+
description: {
|
|
16451
|
+
type: "string",
|
|
16452
|
+
description: "New description (pass empty string to clear)"
|
|
16453
|
+
},
|
|
16454
|
+
people: {
|
|
16455
|
+
type: "array",
|
|
16456
|
+
items: { type: "string" },
|
|
16457
|
+
description: "New people list (replaces existing)"
|
|
16458
|
+
},
|
|
16459
|
+
dueDate: {
|
|
16460
|
+
type: "string",
|
|
16461
|
+
description: "New due date in ISO format YYYY-MM-DD (pass empty string to clear)"
|
|
16462
|
+
},
|
|
16463
|
+
tags: {
|
|
16464
|
+
type: "array",
|
|
16465
|
+
items: { type: "string" },
|
|
16466
|
+
description: "New tags list (replaces existing)"
|
|
16467
|
+
},
|
|
16468
|
+
status: {
|
|
16469
|
+
type: "string",
|
|
16470
|
+
enum: ["pending", "done"],
|
|
16471
|
+
description: "New status"
|
|
16472
|
+
}
|
|
16473
|
+
},
|
|
16474
|
+
required: ["id"]
|
|
16475
|
+
}
|
|
16476
|
+
}
|
|
16477
|
+
};
|
|
16478
|
+
var todoRemoveDefinition = {
|
|
16479
|
+
type: "function",
|
|
16480
|
+
function: {
|
|
16481
|
+
name: "todo_remove",
|
|
16482
|
+
description: "Delete a TODO item.",
|
|
16483
|
+
parameters: {
|
|
16484
|
+
type: "object",
|
|
16485
|
+
properties: {
|
|
16486
|
+
id: {
|
|
16487
|
+
type: "string",
|
|
16488
|
+
description: 'The todo ID to remove (e.g. "todo_a1b2c3")'
|
|
16489
|
+
}
|
|
16490
|
+
},
|
|
16491
|
+
required: ["id"]
|
|
16492
|
+
}
|
|
16493
|
+
}
|
|
16494
|
+
};
|
|
15882
16495
|
function validateKey2(key) {
|
|
15883
16496
|
if (typeof key !== "string") return "Key must be a string";
|
|
15884
16497
|
const trimmed = key.trim();
|
|
@@ -15902,6 +16515,37 @@ function buildStorageContext(toolContext) {
|
|
|
15902
16515
|
if (toolContext?.userId) return { userId: toolContext.userId };
|
|
15903
16516
|
return void 0;
|
|
15904
16517
|
}
|
|
16518
|
+
var TODO_KEY_PREFIX = "todo_";
|
|
16519
|
+
var INTERNAL_KEY_PREFIX = "_";
|
|
16520
|
+
function isTodoEntry(entry) {
|
|
16521
|
+
return entry.id.startsWith(TODO_KEY_PREFIX);
|
|
16522
|
+
}
|
|
16523
|
+
function isInternalEntry(entry) {
|
|
16524
|
+
return entry.id.startsWith(INTERNAL_KEY_PREFIX);
|
|
16525
|
+
}
|
|
16526
|
+
function generateTodoId() {
|
|
16527
|
+
const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
16528
|
+
let id = "";
|
|
16529
|
+
for (let i = 0; i < 6; i++) {
|
|
16530
|
+
id += chars[Math.floor(Math.random() * chars.length)];
|
|
16531
|
+
}
|
|
16532
|
+
return `${TODO_KEY_PREFIX}${id}`;
|
|
16533
|
+
}
|
|
16534
|
+
function renderTodoEntry(entry) {
|
|
16535
|
+
const val = entry.value;
|
|
16536
|
+
const checkbox = val.status === "done" ? "[x]" : "[ ]";
|
|
16537
|
+
const parts = [];
|
|
16538
|
+
if (val.dueDate) parts.push(`due: ${val.dueDate}`);
|
|
16539
|
+
if (val.people && val.people.length > 0) parts.push(`people: ${val.people.join(", ")}`);
|
|
16540
|
+
const meta = parts.length > 0 ? ` (${parts.join(", ")})` : "";
|
|
16541
|
+
const tags = val.tags && val.tags.length > 0 ? ` [${val.tags.join(", ")}]` : "";
|
|
16542
|
+
let line = `- ${checkbox} ${entry.id}: ${val.title}${meta}${tags}`;
|
|
16543
|
+
if (val.description) {
|
|
16544
|
+
line += `
|
|
16545
|
+
${val.description}`;
|
|
16546
|
+
}
|
|
16547
|
+
return line;
|
|
16548
|
+
}
|
|
15905
16549
|
function formatValue(value) {
|
|
15906
16550
|
if (value === null) return "null";
|
|
15907
16551
|
if (typeof value === "string") return value;
|
|
@@ -15969,7 +16613,10 @@ var UserInfoPluginNextGen = class {
|
|
|
15969
16613
|
this.createUserInfoSetTool(),
|
|
15970
16614
|
this.createUserInfoGetTool(),
|
|
15971
16615
|
this.createUserInfoRemoveTool(),
|
|
15972
|
-
this.createUserInfoClearTool()
|
|
16616
|
+
this.createUserInfoClearTool(),
|
|
16617
|
+
this.createTodoAddTool(),
|
|
16618
|
+
this.createTodoUpdateTool(),
|
|
16619
|
+
this.createTodoRemoveTool()
|
|
15973
16620
|
];
|
|
15974
16621
|
}
|
|
15975
16622
|
destroy() {
|
|
@@ -16040,9 +16687,38 @@ var UserInfoPluginNextGen = class {
|
|
|
16040
16687
|
* Render entries as markdown for context injection
|
|
16041
16688
|
*/
|
|
16042
16689
|
renderContent() {
|
|
16043
|
-
const
|
|
16044
|
-
|
|
16045
|
-
|
|
16690
|
+
const userEntries = [];
|
|
16691
|
+
const todoEntries = [];
|
|
16692
|
+
for (const entry of this._entries.values()) {
|
|
16693
|
+
if (isTodoEntry(entry)) {
|
|
16694
|
+
todoEntries.push(entry);
|
|
16695
|
+
} else if (!isInternalEntry(entry)) {
|
|
16696
|
+
userEntries.push(entry);
|
|
16697
|
+
}
|
|
16698
|
+
}
|
|
16699
|
+
const sections = [];
|
|
16700
|
+
if (userEntries.length > 0) {
|
|
16701
|
+
userEntries.sort((a, b) => a.createdAt - b.createdAt);
|
|
16702
|
+
sections.push(
|
|
16703
|
+
userEntries.map((entry) => `### ${entry.id}
|
|
16704
|
+
${formatValue(entry.value)}`).join("\n\n")
|
|
16705
|
+
);
|
|
16706
|
+
}
|
|
16707
|
+
if (todoEntries.length > 0) {
|
|
16708
|
+
todoEntries.sort((a, b) => {
|
|
16709
|
+
const aVal = a.value;
|
|
16710
|
+
const bVal = b.value;
|
|
16711
|
+
if (aVal.status !== bVal.status) return aVal.status === "pending" ? -1 : 1;
|
|
16712
|
+
if (aVal.dueDate && bVal.dueDate) return aVal.dueDate.localeCompare(bVal.dueDate);
|
|
16713
|
+
if (aVal.dueDate) return -1;
|
|
16714
|
+
if (bVal.dueDate) return 1;
|
|
16715
|
+
return a.createdAt - b.createdAt;
|
|
16716
|
+
});
|
|
16717
|
+
sections.push(
|
|
16718
|
+
"## Current TODOs\n" + todoEntries.map(renderTodoEntry).join("\n")
|
|
16719
|
+
);
|
|
16720
|
+
}
|
|
16721
|
+
return sections.join("\n\n");
|
|
16046
16722
|
}
|
|
16047
16723
|
/**
|
|
16048
16724
|
* Resolve storage instance (lazy singleton)
|
|
@@ -16223,6 +16899,619 @@ ${formatValue(entry.value)}`).join("\n\n");
|
|
|
16223
16899
|
describeCall: () => "clear user info"
|
|
16224
16900
|
};
|
|
16225
16901
|
}
|
|
16902
|
+
// ============================================================================
|
|
16903
|
+
// TODO Tool Factories
|
|
16904
|
+
// ============================================================================
|
|
16905
|
+
createTodoAddTool() {
|
|
16906
|
+
return {
|
|
16907
|
+
definition: todoAddDefinition,
|
|
16908
|
+
execute: async (args, context) => {
|
|
16909
|
+
this.assertNotDestroyed();
|
|
16910
|
+
await this.ensureInitialized();
|
|
16911
|
+
const userId = context?.userId ?? this.userId;
|
|
16912
|
+
const title = args.title;
|
|
16913
|
+
if (!title || typeof title !== "string" || title.trim().length === 0) {
|
|
16914
|
+
return { error: "Title is required" };
|
|
16915
|
+
}
|
|
16916
|
+
if (this._entries.size >= this.maxEntries) {
|
|
16917
|
+
return { error: `Maximum number of entries reached (${this.maxEntries})` };
|
|
16918
|
+
}
|
|
16919
|
+
let todoId = generateTodoId();
|
|
16920
|
+
while (this._entries.has(todoId)) {
|
|
16921
|
+
todoId = generateTodoId();
|
|
16922
|
+
}
|
|
16923
|
+
const todoValue = {
|
|
16924
|
+
type: "todo",
|
|
16925
|
+
title: title.trim(),
|
|
16926
|
+
description: args.description ? String(args.description).trim() : void 0,
|
|
16927
|
+
people: Array.isArray(args.people) ? args.people.filter((p) => typeof p === "string") : void 0,
|
|
16928
|
+
dueDate: typeof args.dueDate === "string" && args.dueDate.trim() ? args.dueDate.trim() : void 0,
|
|
16929
|
+
tags: Array.isArray(args.tags) ? args.tags.filter((t) => typeof t === "string") : void 0,
|
|
16930
|
+
status: "pending"
|
|
16931
|
+
};
|
|
16932
|
+
const valueSize = calculateValueSize(todoValue);
|
|
16933
|
+
let currentTotal = 0;
|
|
16934
|
+
for (const e of this._entries.values()) {
|
|
16935
|
+
currentTotal += calculateValueSize(e.value);
|
|
16936
|
+
}
|
|
16937
|
+
if (currentTotal + valueSize > this.maxTotalSize) {
|
|
16938
|
+
return { error: `Total size would exceed maximum (${this.maxTotalSize} bytes)` };
|
|
16939
|
+
}
|
|
16940
|
+
const now = Date.now();
|
|
16941
|
+
const entry = {
|
|
16942
|
+
id: todoId,
|
|
16943
|
+
value: todoValue,
|
|
16944
|
+
valueType: "object",
|
|
16945
|
+
description: title.trim(),
|
|
16946
|
+
createdAt: now,
|
|
16947
|
+
updatedAt: now
|
|
16948
|
+
};
|
|
16949
|
+
this._entries.set(todoId, entry);
|
|
16950
|
+
this._tokenCache = null;
|
|
16951
|
+
await this.persistToStorage(userId);
|
|
16952
|
+
return {
|
|
16953
|
+
success: true,
|
|
16954
|
+
message: `TODO '${title.trim()}' created`,
|
|
16955
|
+
id: todoId,
|
|
16956
|
+
todo: todoValue
|
|
16957
|
+
};
|
|
16958
|
+
},
|
|
16959
|
+
permission: { scope: "always", riskLevel: "low" },
|
|
16960
|
+
describeCall: (args) => `add todo '${args.title}'`
|
|
16961
|
+
};
|
|
16962
|
+
}
|
|
16963
|
+
createTodoUpdateTool() {
|
|
16964
|
+
return {
|
|
16965
|
+
definition: todoUpdateDefinition,
|
|
16966
|
+
execute: async (args, context) => {
|
|
16967
|
+
this.assertNotDestroyed();
|
|
16968
|
+
await this.ensureInitialized();
|
|
16969
|
+
const userId = context?.userId ?? this.userId;
|
|
16970
|
+
const id = args.id;
|
|
16971
|
+
if (!id || typeof id !== "string") {
|
|
16972
|
+
return { error: "Todo ID is required" };
|
|
16973
|
+
}
|
|
16974
|
+
const entry = this._entries.get(id);
|
|
16975
|
+
if (!entry || !isTodoEntry(entry)) {
|
|
16976
|
+
return { error: `TODO '${id}' not found` };
|
|
16977
|
+
}
|
|
16978
|
+
const currentValue = entry.value;
|
|
16979
|
+
const updatedValue = { ...currentValue };
|
|
16980
|
+
if (typeof args.title === "string" && args.title.trim()) {
|
|
16981
|
+
updatedValue.title = args.title.trim();
|
|
16982
|
+
}
|
|
16983
|
+
if (args.description !== void 0) {
|
|
16984
|
+
updatedValue.description = typeof args.description === "string" && args.description.trim() ? args.description.trim() : void 0;
|
|
16985
|
+
}
|
|
16986
|
+
if (args.people !== void 0) {
|
|
16987
|
+
updatedValue.people = Array.isArray(args.people) ? args.people.filter((p) => typeof p === "string") : void 0;
|
|
16988
|
+
}
|
|
16989
|
+
if (args.dueDate !== void 0) {
|
|
16990
|
+
updatedValue.dueDate = typeof args.dueDate === "string" && args.dueDate.trim() ? args.dueDate.trim() : void 0;
|
|
16991
|
+
}
|
|
16992
|
+
if (args.tags !== void 0) {
|
|
16993
|
+
updatedValue.tags = Array.isArray(args.tags) ? args.tags.filter((t) => typeof t === "string") : void 0;
|
|
16994
|
+
}
|
|
16995
|
+
if (args.status === "pending" || args.status === "done") {
|
|
16996
|
+
updatedValue.status = args.status;
|
|
16997
|
+
}
|
|
16998
|
+
const valueSize = calculateValueSize(updatedValue);
|
|
16999
|
+
let currentTotal = 0;
|
|
17000
|
+
for (const e of this._entries.values()) {
|
|
17001
|
+
currentTotal += calculateValueSize(e.value);
|
|
17002
|
+
}
|
|
17003
|
+
const existingSize = calculateValueSize(currentValue);
|
|
17004
|
+
if (currentTotal - existingSize + valueSize > this.maxTotalSize) {
|
|
17005
|
+
return { error: `Total size would exceed maximum (${this.maxTotalSize} bytes)` };
|
|
17006
|
+
}
|
|
17007
|
+
const now = Date.now();
|
|
17008
|
+
const updatedEntry = {
|
|
17009
|
+
...entry,
|
|
17010
|
+
value: updatedValue,
|
|
17011
|
+
description: updatedValue.title,
|
|
17012
|
+
updatedAt: now
|
|
17013
|
+
};
|
|
17014
|
+
this._entries.set(id, updatedEntry);
|
|
17015
|
+
this._tokenCache = null;
|
|
17016
|
+
await this.persistToStorage(userId);
|
|
17017
|
+
return {
|
|
17018
|
+
success: true,
|
|
17019
|
+
message: `TODO '${id}' updated`,
|
|
17020
|
+
id,
|
|
17021
|
+
todo: updatedValue
|
|
17022
|
+
};
|
|
17023
|
+
},
|
|
17024
|
+
permission: { scope: "always", riskLevel: "low" },
|
|
17025
|
+
describeCall: (args) => `update todo '${args.id}'`
|
|
17026
|
+
};
|
|
17027
|
+
}
|
|
17028
|
+
createTodoRemoveTool() {
|
|
17029
|
+
return {
|
|
17030
|
+
definition: todoRemoveDefinition,
|
|
17031
|
+
execute: async (args, context) => {
|
|
17032
|
+
this.assertNotDestroyed();
|
|
17033
|
+
await this.ensureInitialized();
|
|
17034
|
+
const userId = context?.userId ?? this.userId;
|
|
17035
|
+
const id = args.id;
|
|
17036
|
+
if (!id || typeof id !== "string") {
|
|
17037
|
+
return { error: "Todo ID is required" };
|
|
17038
|
+
}
|
|
17039
|
+
const entry = this._entries.get(id);
|
|
17040
|
+
if (!entry || !isTodoEntry(entry)) {
|
|
17041
|
+
return { error: `TODO '${id}' not found` };
|
|
17042
|
+
}
|
|
17043
|
+
this._entries.delete(id);
|
|
17044
|
+
this._tokenCache = null;
|
|
17045
|
+
await this.persistToStorage(userId);
|
|
17046
|
+
return {
|
|
17047
|
+
success: true,
|
|
17048
|
+
message: `TODO '${id}' removed`,
|
|
17049
|
+
id
|
|
17050
|
+
};
|
|
17051
|
+
},
|
|
17052
|
+
permission: { scope: "always", riskLevel: "low" },
|
|
17053
|
+
describeCall: (args) => `remove todo '${args.id}'`
|
|
17054
|
+
};
|
|
17055
|
+
}
|
|
17056
|
+
};
|
|
17057
|
+
|
|
17058
|
+
// src/core/context-nextgen/plugins/ToolCatalogPluginNextGen.ts
|
|
17059
|
+
init_Logger();
|
|
17060
|
+
var DEFAULT_MAX_LOADED = 10;
|
|
17061
|
+
var TOOL_CATALOG_INSTRUCTIONS = `## Tool Catalog
|
|
17062
|
+
|
|
17063
|
+
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:
|
|
17064
|
+
|
|
17065
|
+
**tool_catalog_search** \u2014 Browse available tool categories and search for specific tools.
|
|
17066
|
+
- No params \u2192 list all available categories with descriptions
|
|
17067
|
+
- \`category\` \u2192 list tools in that category
|
|
17068
|
+
- \`query\` \u2192 keyword search across categories and tools
|
|
17069
|
+
|
|
17070
|
+
**tool_catalog_load** \u2014 Load a category's tools so you can use them.
|
|
17071
|
+
- Tools become available immediately after loading.
|
|
17072
|
+
- If you need tools from a category, load it first.
|
|
17073
|
+
|
|
17074
|
+
**tool_catalog_unload** \u2014 Unload a category to free token budget.
|
|
17075
|
+
- Unloaded tools are no longer sent to you.
|
|
17076
|
+
- Use when you're done with a category.
|
|
17077
|
+
|
|
17078
|
+
**Best practices:**
|
|
17079
|
+
- Search first to find the right category before loading.
|
|
17080
|
+
- Unload categories you no longer need to keep context lean.
|
|
17081
|
+
- Categories marked [LOADED] are already available.`;
|
|
17082
|
+
var catalogSearchDefinition = {
|
|
17083
|
+
type: "function",
|
|
17084
|
+
function: {
|
|
17085
|
+
name: "tool_catalog_search",
|
|
17086
|
+
description: "Search the tool catalog. No params lists categories. Use category to list tools in it, or query to keyword-search.",
|
|
17087
|
+
parameters: {
|
|
17088
|
+
type: "object",
|
|
17089
|
+
properties: {
|
|
17090
|
+
query: {
|
|
17091
|
+
type: "string",
|
|
17092
|
+
description: "Keyword to search across category names, descriptions, and tool names"
|
|
17093
|
+
},
|
|
17094
|
+
category: {
|
|
17095
|
+
type: "string",
|
|
17096
|
+
description: "Category name to list its tools"
|
|
17097
|
+
}
|
|
17098
|
+
}
|
|
17099
|
+
}
|
|
17100
|
+
}
|
|
17101
|
+
};
|
|
17102
|
+
var catalogLoadDefinition = {
|
|
17103
|
+
type: "function",
|
|
17104
|
+
function: {
|
|
17105
|
+
name: "tool_catalog_load",
|
|
17106
|
+
description: "Load all tools from a category so they become available for use.",
|
|
17107
|
+
parameters: {
|
|
17108
|
+
type: "object",
|
|
17109
|
+
properties: {
|
|
17110
|
+
category: {
|
|
17111
|
+
type: "string",
|
|
17112
|
+
description: "Category name to load"
|
|
17113
|
+
}
|
|
17114
|
+
},
|
|
17115
|
+
required: ["category"]
|
|
17116
|
+
}
|
|
17117
|
+
}
|
|
17118
|
+
};
|
|
17119
|
+
var catalogUnloadDefinition = {
|
|
17120
|
+
type: "function",
|
|
17121
|
+
function: {
|
|
17122
|
+
name: "tool_catalog_unload",
|
|
17123
|
+
description: "Unload a category to free token budget. Tools from this category will no longer be available.",
|
|
17124
|
+
parameters: {
|
|
17125
|
+
type: "object",
|
|
17126
|
+
properties: {
|
|
17127
|
+
category: {
|
|
17128
|
+
type: "string",
|
|
17129
|
+
description: "Category name to unload"
|
|
17130
|
+
}
|
|
17131
|
+
},
|
|
17132
|
+
required: ["category"]
|
|
17133
|
+
}
|
|
17134
|
+
}
|
|
17135
|
+
};
|
|
17136
|
+
var ToolCatalogPluginNextGen = class extends BasePluginNextGen {
|
|
17137
|
+
name = "tool_catalog";
|
|
17138
|
+
/** category name → array of tool names that were loaded */
|
|
17139
|
+
_loadedCategories = /* @__PURE__ */ new Map();
|
|
17140
|
+
/** Reference to the ToolManager for registering/disabling tools */
|
|
17141
|
+
_toolManager = null;
|
|
17142
|
+
/** Cached connector categories — discovered once in setToolManager() */
|
|
17143
|
+
_connectorCategories = null;
|
|
17144
|
+
/** Whether this plugin has been destroyed */
|
|
17145
|
+
_destroyed = false;
|
|
17146
|
+
/** WeakMap cache for tool definition token estimates */
|
|
17147
|
+
_toolTokenCache = /* @__PURE__ */ new WeakMap();
|
|
17148
|
+
_config;
|
|
17149
|
+
constructor(config) {
|
|
17150
|
+
super();
|
|
17151
|
+
this._config = {
|
|
17152
|
+
maxLoadedCategories: DEFAULT_MAX_LOADED,
|
|
17153
|
+
...config
|
|
17154
|
+
};
|
|
17155
|
+
}
|
|
17156
|
+
// ========================================================================
|
|
17157
|
+
// Plugin Interface
|
|
17158
|
+
// ========================================================================
|
|
17159
|
+
getInstructions() {
|
|
17160
|
+
return TOOL_CATALOG_INSTRUCTIONS;
|
|
17161
|
+
}
|
|
17162
|
+
async getContent() {
|
|
17163
|
+
const categories = this.getAllowedCategories();
|
|
17164
|
+
if (categories.length === 0 && this.getConnectorCategories().length === 0) return null;
|
|
17165
|
+
const lines = ["## Tool Catalog"];
|
|
17166
|
+
lines.push("");
|
|
17167
|
+
const loaded = Array.from(this._loadedCategories.keys());
|
|
17168
|
+
if (loaded.length > 0) {
|
|
17169
|
+
lines.push(`**Loaded:** ${loaded.join(", ")}`);
|
|
17170
|
+
}
|
|
17171
|
+
lines.push(`**Available categories:** ${categories.length}`);
|
|
17172
|
+
for (const cat of categories) {
|
|
17173
|
+
const tools = ToolCatalogRegistry.getToolsInCategory(cat.name);
|
|
17174
|
+
const marker = this._loadedCategories.has(cat.name) ? " [LOADED]" : "";
|
|
17175
|
+
lines.push(`- **${cat.displayName}** (${tools.length} tools)${marker}: ${cat.description}`);
|
|
17176
|
+
}
|
|
17177
|
+
for (const cc of this.getConnectorCategories()) {
|
|
17178
|
+
const marker = this._loadedCategories.has(cc.name) ? " [LOADED]" : "";
|
|
17179
|
+
lines.push(`- **${cc.displayName}** (${cc.toolCount} tools)${marker}: ${cc.description}`);
|
|
17180
|
+
}
|
|
17181
|
+
const content = lines.join("\n");
|
|
17182
|
+
this.updateTokenCache(this.estimator.estimateTokens(content));
|
|
17183
|
+
return content;
|
|
17184
|
+
}
|
|
17185
|
+
getContents() {
|
|
17186
|
+
return {
|
|
17187
|
+
loadedCategories: Array.from(this._loadedCategories.entries()).map(([name, tools]) => ({
|
|
17188
|
+
category: name,
|
|
17189
|
+
toolCount: tools.length,
|
|
17190
|
+
tools
|
|
17191
|
+
}))
|
|
17192
|
+
};
|
|
17193
|
+
}
|
|
17194
|
+
getTools() {
|
|
17195
|
+
const plugin = this;
|
|
17196
|
+
const searchTool = {
|
|
17197
|
+
definition: catalogSearchDefinition,
|
|
17198
|
+
execute: async (args) => {
|
|
17199
|
+
return plugin.executeSearch(args.query, args.category);
|
|
17200
|
+
}
|
|
17201
|
+
};
|
|
17202
|
+
const loadTool = {
|
|
17203
|
+
definition: catalogLoadDefinition,
|
|
17204
|
+
execute: async (args) => {
|
|
17205
|
+
return plugin.executeLoad(args.category);
|
|
17206
|
+
}
|
|
17207
|
+
};
|
|
17208
|
+
const unloadTool = {
|
|
17209
|
+
definition: catalogUnloadDefinition,
|
|
17210
|
+
execute: async (args) => {
|
|
17211
|
+
return plugin.executeUnload(args.category);
|
|
17212
|
+
}
|
|
17213
|
+
};
|
|
17214
|
+
return [searchTool, loadTool, unloadTool];
|
|
17215
|
+
}
|
|
17216
|
+
isCompactable() {
|
|
17217
|
+
return this._loadedCategories.size > 0;
|
|
17218
|
+
}
|
|
17219
|
+
async compact(targetTokensToFree) {
|
|
17220
|
+
if (!this._toolManager || this._loadedCategories.size === 0) return 0;
|
|
17221
|
+
const categoriesByLastUsed = this.getCategoriesSortedByLastUsed();
|
|
17222
|
+
let freed = 0;
|
|
17223
|
+
for (const category of categoriesByLastUsed) {
|
|
17224
|
+
if (freed >= targetTokensToFree) break;
|
|
17225
|
+
const toolNames = this._loadedCategories.get(category);
|
|
17226
|
+
if (!toolNames) continue;
|
|
17227
|
+
const toolTokens = this.estimateToolDefinitionTokens(toolNames);
|
|
17228
|
+
this._toolManager.setEnabled(toolNames, false);
|
|
17229
|
+
this._loadedCategories.delete(category);
|
|
17230
|
+
freed += toolTokens;
|
|
17231
|
+
exports.logger.debug(
|
|
17232
|
+
{ category, toolCount: toolNames.length, freed: toolTokens },
|
|
17233
|
+
`[ToolCatalogPlugin] Compacted category '${category}'`
|
|
17234
|
+
);
|
|
17235
|
+
}
|
|
17236
|
+
this.invalidateTokenCache();
|
|
17237
|
+
return freed;
|
|
17238
|
+
}
|
|
17239
|
+
getState() {
|
|
17240
|
+
return {
|
|
17241
|
+
loadedCategories: Array.from(this._loadedCategories.keys())
|
|
17242
|
+
};
|
|
17243
|
+
}
|
|
17244
|
+
restoreState(state) {
|
|
17245
|
+
if (!state || typeof state !== "object") return;
|
|
17246
|
+
const s = state;
|
|
17247
|
+
if (!Array.isArray(s.loadedCategories) || s.loadedCategories.length === 0) return;
|
|
17248
|
+
for (const category of s.loadedCategories) {
|
|
17249
|
+
if (typeof category !== "string" || !category) continue;
|
|
17250
|
+
const result = this.executeLoad(category);
|
|
17251
|
+
if (result.error) {
|
|
17252
|
+
exports.logger.warn(
|
|
17253
|
+
{ category, error: result.error },
|
|
17254
|
+
`[ToolCatalogPlugin] Failed to restore category '${category}'`
|
|
17255
|
+
);
|
|
17256
|
+
}
|
|
17257
|
+
}
|
|
17258
|
+
this.invalidateTokenCache();
|
|
17259
|
+
}
|
|
17260
|
+
destroy() {
|
|
17261
|
+
this._loadedCategories.clear();
|
|
17262
|
+
this._toolManager = null;
|
|
17263
|
+
this._connectorCategories = null;
|
|
17264
|
+
this._destroyed = true;
|
|
17265
|
+
}
|
|
17266
|
+
// ========================================================================
|
|
17267
|
+
// Public API
|
|
17268
|
+
// ========================================================================
|
|
17269
|
+
/**
|
|
17270
|
+
* Set the ToolManager reference. Called by AgentContextNextGen after plugin registration.
|
|
17271
|
+
*/
|
|
17272
|
+
setToolManager(tm) {
|
|
17273
|
+
this._toolManager = tm;
|
|
17274
|
+
this._connectorCategories = ToolCatalogRegistry.discoverConnectorCategories({
|
|
17275
|
+
scope: this._config.categoryScope,
|
|
17276
|
+
identities: this._config.identities
|
|
17277
|
+
});
|
|
17278
|
+
if (this._config.autoLoadCategories?.length) {
|
|
17279
|
+
for (const category of this._config.autoLoadCategories) {
|
|
17280
|
+
const result = this.executeLoad(category);
|
|
17281
|
+
if (result.error) {
|
|
17282
|
+
exports.logger.warn(
|
|
17283
|
+
{ category, error: result.error },
|
|
17284
|
+
`[ToolCatalogPlugin] Failed to auto-load category '${category}'`
|
|
17285
|
+
);
|
|
17286
|
+
}
|
|
17287
|
+
}
|
|
17288
|
+
}
|
|
17289
|
+
}
|
|
17290
|
+
/** Get list of currently loaded category names */
|
|
17291
|
+
get loadedCategories() {
|
|
17292
|
+
return Array.from(this._loadedCategories.keys());
|
|
17293
|
+
}
|
|
17294
|
+
// ========================================================================
|
|
17295
|
+
// Metatool Implementations
|
|
17296
|
+
// ========================================================================
|
|
17297
|
+
executeSearch(query, category) {
|
|
17298
|
+
if (this._destroyed) return { error: "Plugin destroyed" };
|
|
17299
|
+
if (category) {
|
|
17300
|
+
if (ToolCatalogRegistry.parseConnectorCategory(category) !== null) {
|
|
17301
|
+
return this.searchConnectorCategory(category);
|
|
17302
|
+
}
|
|
17303
|
+
if (!ToolCatalogRegistry.hasCategory(category)) {
|
|
17304
|
+
return { error: `Category '${category}' not found. Use tool_catalog_search with no params to see available categories.` };
|
|
17305
|
+
}
|
|
17306
|
+
if (!ToolCatalogRegistry.isCategoryAllowed(category, this._config.categoryScope)) {
|
|
17307
|
+
return { error: `Category '${category}' is not available for this agent.` };
|
|
17308
|
+
}
|
|
17309
|
+
const tools = ToolCatalogRegistry.getToolsInCategory(category);
|
|
17310
|
+
const loaded = this._loadedCategories.has(category);
|
|
17311
|
+
return {
|
|
17312
|
+
category,
|
|
17313
|
+
loaded,
|
|
17314
|
+
tools: tools.map((t) => ({
|
|
17315
|
+
name: t.name,
|
|
17316
|
+
displayName: t.displayName,
|
|
17317
|
+
description: t.description,
|
|
17318
|
+
safeByDefault: t.safeByDefault
|
|
17319
|
+
}))
|
|
17320
|
+
};
|
|
17321
|
+
}
|
|
17322
|
+
if (query) {
|
|
17323
|
+
return this.keywordSearch(query);
|
|
17324
|
+
}
|
|
17325
|
+
const categories = this.getAllowedCategories();
|
|
17326
|
+
const connectorCats = this.getConnectorCategories();
|
|
17327
|
+
const result = [];
|
|
17328
|
+
for (const cat of categories) {
|
|
17329
|
+
const tools = ToolCatalogRegistry.getToolsInCategory(cat.name);
|
|
17330
|
+
result.push({
|
|
17331
|
+
name: cat.name,
|
|
17332
|
+
displayName: cat.displayName,
|
|
17333
|
+
description: cat.description,
|
|
17334
|
+
toolCount: tools.length,
|
|
17335
|
+
loaded: this._loadedCategories.has(cat.name)
|
|
17336
|
+
});
|
|
17337
|
+
}
|
|
17338
|
+
for (const cc of connectorCats) {
|
|
17339
|
+
result.push({
|
|
17340
|
+
name: cc.name,
|
|
17341
|
+
displayName: cc.displayName,
|
|
17342
|
+
description: cc.description,
|
|
17343
|
+
toolCount: cc.toolCount,
|
|
17344
|
+
loaded: this._loadedCategories.has(cc.name)
|
|
17345
|
+
});
|
|
17346
|
+
}
|
|
17347
|
+
return { categories: result };
|
|
17348
|
+
}
|
|
17349
|
+
executeLoad(category) {
|
|
17350
|
+
if (this._destroyed) return { error: "Plugin destroyed" };
|
|
17351
|
+
if (!this._toolManager) {
|
|
17352
|
+
return { error: "ToolManager not connected. Plugin not properly initialized." };
|
|
17353
|
+
}
|
|
17354
|
+
if (!ToolCatalogRegistry.isCategoryAllowed(category, this._config.categoryScope)) {
|
|
17355
|
+
return { error: `Category '${category}' is not available for this agent.` };
|
|
17356
|
+
}
|
|
17357
|
+
if (this._loadedCategories.has(category)) {
|
|
17358
|
+
const toolNames2 = this._loadedCategories.get(category);
|
|
17359
|
+
return { loaded: toolNames2.length, tools: toolNames2, alreadyLoaded: true };
|
|
17360
|
+
}
|
|
17361
|
+
if (this._loadedCategories.size >= this._config.maxLoadedCategories) {
|
|
17362
|
+
return {
|
|
17363
|
+
error: `Maximum loaded categories (${this._config.maxLoadedCategories}) reached. Unload a category first.`,
|
|
17364
|
+
loaded: Array.from(this._loadedCategories.keys())
|
|
17365
|
+
};
|
|
17366
|
+
}
|
|
17367
|
+
const isConnector = ToolCatalogRegistry.parseConnectorCategory(category) !== null;
|
|
17368
|
+
let tools;
|
|
17369
|
+
if (isConnector) {
|
|
17370
|
+
tools = ToolCatalogRegistry.resolveConnectorCategoryTools(category);
|
|
17371
|
+
} else {
|
|
17372
|
+
const entries = ToolCatalogRegistry.getToolsInCategory(category);
|
|
17373
|
+
if (entries.length === 0) {
|
|
17374
|
+
return { error: `Category '${category}' has no tools or does not exist.` };
|
|
17375
|
+
}
|
|
17376
|
+
tools = entries.filter((e) => e.tool != null).map((e) => ({ tool: e.tool, name: e.name }));
|
|
17377
|
+
}
|
|
17378
|
+
if (tools.length === 0) {
|
|
17379
|
+
return { error: `No tools found for category '${category}'.` };
|
|
17380
|
+
}
|
|
17381
|
+
const toolNames = [];
|
|
17382
|
+
for (const { tool, name } of tools) {
|
|
17383
|
+
const existing = this._toolManager.getRegistration(name);
|
|
17384
|
+
if (existing) {
|
|
17385
|
+
this._toolManager.setEnabled([name], true);
|
|
17386
|
+
} else {
|
|
17387
|
+
this._toolManager.register(tool, { category, source: `catalog:${category}` });
|
|
17388
|
+
}
|
|
17389
|
+
toolNames.push(name);
|
|
17390
|
+
}
|
|
17391
|
+
this._loadedCategories.set(category, toolNames);
|
|
17392
|
+
this.invalidateTokenCache();
|
|
17393
|
+
exports.logger.debug(
|
|
17394
|
+
{ category, toolCount: toolNames.length, tools: toolNames },
|
|
17395
|
+
`[ToolCatalogPlugin] Loaded category '${category}'`
|
|
17396
|
+
);
|
|
17397
|
+
return { loaded: toolNames.length, tools: toolNames };
|
|
17398
|
+
}
|
|
17399
|
+
executeUnload(category) {
|
|
17400
|
+
if (this._destroyed) return { error: "Plugin destroyed" };
|
|
17401
|
+
if (!this._toolManager) {
|
|
17402
|
+
return { error: "ToolManager not connected." };
|
|
17403
|
+
}
|
|
17404
|
+
const toolNames = this._loadedCategories.get(category);
|
|
17405
|
+
if (!toolNames) {
|
|
17406
|
+
return { unloaded: 0, message: `Category '${category}' is not loaded.` };
|
|
17407
|
+
}
|
|
17408
|
+
this._toolManager.setEnabled(toolNames, false);
|
|
17409
|
+
this._loadedCategories.delete(category);
|
|
17410
|
+
this.invalidateTokenCache();
|
|
17411
|
+
exports.logger.debug(
|
|
17412
|
+
{ category, toolCount: toolNames.length },
|
|
17413
|
+
`[ToolCatalogPlugin] Unloaded category '${category}'`
|
|
17414
|
+
);
|
|
17415
|
+
return { unloaded: toolNames.length };
|
|
17416
|
+
}
|
|
17417
|
+
// ========================================================================
|
|
17418
|
+
// Helpers
|
|
17419
|
+
// ========================================================================
|
|
17420
|
+
getAllowedCategories() {
|
|
17421
|
+
return ToolCatalogRegistry.filterCategories(this._config.categoryScope);
|
|
17422
|
+
}
|
|
17423
|
+
/**
|
|
17424
|
+
* Get connector categories from cache (populated once in setToolManager).
|
|
17425
|
+
*/
|
|
17426
|
+
getConnectorCategories() {
|
|
17427
|
+
return this._connectorCategories ?? [];
|
|
17428
|
+
}
|
|
17429
|
+
keywordSearch(query) {
|
|
17430
|
+
const lq = query.toLowerCase();
|
|
17431
|
+
const results = [];
|
|
17432
|
+
for (const cat of this.getAllowedCategories()) {
|
|
17433
|
+
const catMatch = cat.name.toLowerCase().includes(lq) || cat.displayName.toLowerCase().includes(lq) || cat.description.toLowerCase().includes(lq);
|
|
17434
|
+
const tools = ToolCatalogRegistry.getToolsInCategory(cat.name);
|
|
17435
|
+
const matchingTools = tools.filter(
|
|
17436
|
+
(t) => t.name.toLowerCase().includes(lq) || t.displayName.toLowerCase().includes(lq) || t.description.toLowerCase().includes(lq)
|
|
17437
|
+
);
|
|
17438
|
+
if (catMatch || matchingTools.length > 0) {
|
|
17439
|
+
results.push({
|
|
17440
|
+
category: cat.name,
|
|
17441
|
+
categoryDisplayName: cat.displayName,
|
|
17442
|
+
tools: (catMatch ? tools : matchingTools).map((t) => ({
|
|
17443
|
+
name: t.name,
|
|
17444
|
+
displayName: t.displayName,
|
|
17445
|
+
description: t.description
|
|
17446
|
+
}))
|
|
17447
|
+
});
|
|
17448
|
+
}
|
|
17449
|
+
}
|
|
17450
|
+
for (const cc of this.getConnectorCategories()) {
|
|
17451
|
+
if (cc.name.toLowerCase().includes(lq) || cc.displayName.toLowerCase().includes(lq) || cc.description.toLowerCase().includes(lq)) {
|
|
17452
|
+
results.push({
|
|
17453
|
+
category: cc.name,
|
|
17454
|
+
categoryDisplayName: cc.displayName,
|
|
17455
|
+
tools: cc.tools.map((t) => ({
|
|
17456
|
+
name: t.definition.function.name,
|
|
17457
|
+
displayName: t.definition.function.name.replace(/_/g, " "),
|
|
17458
|
+
description: t.definition.function.description || ""
|
|
17459
|
+
}))
|
|
17460
|
+
});
|
|
17461
|
+
}
|
|
17462
|
+
}
|
|
17463
|
+
return { query, results, totalMatches: results.length };
|
|
17464
|
+
}
|
|
17465
|
+
searchConnectorCategory(category) {
|
|
17466
|
+
const connectorName = ToolCatalogRegistry.parseConnectorCategory(category);
|
|
17467
|
+
const tools = ToolCatalogRegistry.resolveConnectorCategoryTools(category);
|
|
17468
|
+
const loaded = this._loadedCategories.has(category);
|
|
17469
|
+
return {
|
|
17470
|
+
category,
|
|
17471
|
+
loaded,
|
|
17472
|
+
connectorName,
|
|
17473
|
+
tools: tools.map((t) => ({
|
|
17474
|
+
name: t.name,
|
|
17475
|
+
description: t.tool.definition.function.description || ""
|
|
17476
|
+
}))
|
|
17477
|
+
};
|
|
17478
|
+
}
|
|
17479
|
+
getCategoriesSortedByLastUsed() {
|
|
17480
|
+
if (!this._toolManager) return Array.from(this._loadedCategories.keys());
|
|
17481
|
+
const categoryLastUsed = [];
|
|
17482
|
+
for (const [category, toolNames] of this._loadedCategories) {
|
|
17483
|
+
let maxLastUsed = 0;
|
|
17484
|
+
for (const name of toolNames) {
|
|
17485
|
+
const reg = this._toolManager.getRegistration(name);
|
|
17486
|
+
if (reg?.metadata?.lastUsed) {
|
|
17487
|
+
const ts = reg.metadata.lastUsed instanceof Date ? reg.metadata.lastUsed.getTime() : 0;
|
|
17488
|
+
if (ts > maxLastUsed) maxLastUsed = ts;
|
|
17489
|
+
}
|
|
17490
|
+
}
|
|
17491
|
+
categoryLastUsed.push({ category, lastUsed: maxLastUsed });
|
|
17492
|
+
}
|
|
17493
|
+
categoryLastUsed.sort((a, b) => a.lastUsed - b.lastUsed);
|
|
17494
|
+
return categoryLastUsed.map((c) => c.category);
|
|
17495
|
+
}
|
|
17496
|
+
estimateToolDefinitionTokens(toolNames) {
|
|
17497
|
+
let total = 0;
|
|
17498
|
+
for (const name of toolNames) {
|
|
17499
|
+
const reg = this._toolManager?.getRegistration(name);
|
|
17500
|
+
if (reg) {
|
|
17501
|
+
const defObj = reg.tool.definition;
|
|
17502
|
+
const cached = this._toolTokenCache.get(defObj);
|
|
17503
|
+
if (cached !== void 0) {
|
|
17504
|
+
total += cached;
|
|
17505
|
+
} else {
|
|
17506
|
+
const defStr = JSON.stringify(defObj);
|
|
17507
|
+
const tokens = this.estimator.estimateTokens(defStr);
|
|
17508
|
+
this._toolTokenCache.set(defObj, tokens);
|
|
17509
|
+
total += tokens;
|
|
17510
|
+
}
|
|
17511
|
+
}
|
|
17512
|
+
}
|
|
17513
|
+
return total;
|
|
17514
|
+
}
|
|
16226
17515
|
};
|
|
16227
17516
|
|
|
16228
17517
|
// src/core/context-nextgen/AgentContextNextGen.ts
|
|
@@ -16878,7 +18167,8 @@ var DEFAULT_FEATURES = {
|
|
|
16878
18167
|
workingMemory: true,
|
|
16879
18168
|
inContextMemory: true,
|
|
16880
18169
|
persistentInstructions: false,
|
|
16881
|
-
userInfo: false
|
|
18170
|
+
userInfo: false,
|
|
18171
|
+
toolCatalog: false
|
|
16882
18172
|
};
|
|
16883
18173
|
var DEFAULT_CONFIG2 = {
|
|
16884
18174
|
responseReserve: 4096,
|
|
@@ -16950,6 +18240,7 @@ var AgentContextNextGen = class _AgentContextNextGen extends eventemitter3.Event
|
|
|
16950
18240
|
strategy: config.strategy ?? DEFAULT_CONFIG2.strategy,
|
|
16951
18241
|
features: { ...DEFAULT_FEATURES, ...config.features },
|
|
16952
18242
|
agentId: config.agentId ?? this.generateId(),
|
|
18243
|
+
toolCategories: config.toolCategories,
|
|
16953
18244
|
storage: config.storage
|
|
16954
18245
|
};
|
|
16955
18246
|
this._systemPrompt = config.systemPrompt;
|
|
@@ -17005,6 +18296,16 @@ var AgentContextNextGen = class _AgentContextNextGen extends eventemitter3.Event
|
|
|
17005
18296
|
...uiConfig
|
|
17006
18297
|
}));
|
|
17007
18298
|
}
|
|
18299
|
+
if (features.toolCatalog) {
|
|
18300
|
+
const tcConfig = configs.toolCatalog;
|
|
18301
|
+
const plugin = new ToolCatalogPluginNextGen({
|
|
18302
|
+
categoryScope: this._config.toolCategories,
|
|
18303
|
+
identities: this._identities,
|
|
18304
|
+
...tcConfig
|
|
18305
|
+
});
|
|
18306
|
+
this.registerPlugin(plugin);
|
|
18307
|
+
plugin.setToolManager(this._tools);
|
|
18308
|
+
}
|
|
17008
18309
|
this.validateStrategyDependencies(this._compactionStrategy);
|
|
17009
18310
|
}
|
|
17010
18311
|
/**
|
|
@@ -24726,7 +26027,9 @@ var ROUTINE_KEYS = {
|
|
|
24726
26027
|
/** Running fold accumulator (ICM) */
|
|
24727
26028
|
FOLD_ACCUMULATOR: "__fold_accumulator",
|
|
24728
26029
|
/** Prefix for large dep results stored in WM findings tier */
|
|
24729
|
-
WM_DEP_FINDINGS_PREFIX: "findings/__dep_result_"
|
|
26030
|
+
WM_DEP_FINDINGS_PREFIX: "findings/__dep_result_",
|
|
26031
|
+
/** Prefix for auto-stored task outputs (set by output contracts) */
|
|
26032
|
+
TASK_OUTPUT_PREFIX: "__task_output_"
|
|
24730
26033
|
};
|
|
24731
26034
|
function resolveTemplates(text, inputs, icmPlugin) {
|
|
24732
26035
|
return text.replace(/\{\{(\w+)\.(\w+)\}\}/g, (_match, namespace, key) => {
|
|
@@ -24751,13 +26054,45 @@ function resolveTemplates(text, inputs, icmPlugin) {
|
|
|
24751
26054
|
function resolveTaskTemplates(task, inputs, icmPlugin) {
|
|
24752
26055
|
const resolvedDescription = resolveTemplates(task.description, inputs, icmPlugin);
|
|
24753
26056
|
const resolvedExpectedOutput = task.expectedOutput ? resolveTemplates(task.expectedOutput, inputs, icmPlugin) : task.expectedOutput;
|
|
24754
|
-
|
|
26057
|
+
let resolvedControlFlow = task.controlFlow;
|
|
26058
|
+
if (task.controlFlow && "source" in task.controlFlow) {
|
|
26059
|
+
const flow = task.controlFlow;
|
|
26060
|
+
const source = flow.source;
|
|
26061
|
+
if (typeof source === "string") {
|
|
26062
|
+
const r = resolveTemplates(source, inputs, icmPlugin);
|
|
26063
|
+
if (r !== source) {
|
|
26064
|
+
resolvedControlFlow = { ...flow, source: r };
|
|
26065
|
+
}
|
|
26066
|
+
} else if (source) {
|
|
26067
|
+
let changed = false;
|
|
26068
|
+
const resolved = { ...source };
|
|
26069
|
+
if (resolved.task) {
|
|
26070
|
+
const r = resolveTemplates(resolved.task, inputs, icmPlugin);
|
|
26071
|
+
if (r !== resolved.task) {
|
|
26072
|
+
resolved.task = r;
|
|
26073
|
+
changed = true;
|
|
26074
|
+
}
|
|
26075
|
+
}
|
|
26076
|
+
if (resolved.key) {
|
|
26077
|
+
const r = resolveTemplates(resolved.key, inputs, icmPlugin);
|
|
26078
|
+
if (r !== resolved.key) {
|
|
26079
|
+
resolved.key = r;
|
|
26080
|
+
changed = true;
|
|
26081
|
+
}
|
|
26082
|
+
}
|
|
26083
|
+
if (changed) {
|
|
26084
|
+
resolvedControlFlow = { ...flow, source: resolved };
|
|
26085
|
+
}
|
|
26086
|
+
}
|
|
26087
|
+
}
|
|
26088
|
+
if (resolvedDescription === task.description && resolvedExpectedOutput === task.expectedOutput && resolvedControlFlow === task.controlFlow) {
|
|
24755
26089
|
return task;
|
|
24756
26090
|
}
|
|
24757
26091
|
return {
|
|
24758
26092
|
...task,
|
|
24759
26093
|
description: resolvedDescription,
|
|
24760
|
-
expectedOutput: resolvedExpectedOutput
|
|
26094
|
+
expectedOutput: resolvedExpectedOutput,
|
|
26095
|
+
controlFlow: resolvedControlFlow
|
|
24761
26096
|
};
|
|
24762
26097
|
}
|
|
24763
26098
|
function validateAndResolveInputs(parameters, inputs) {
|
|
@@ -24825,17 +26160,6 @@ function cleanFoldKeys(icmPlugin) {
|
|
|
24825
26160
|
cleanMapKeys(icmPlugin);
|
|
24826
26161
|
icmPlugin.delete(ROUTINE_KEYS.FOLD_ACCUMULATOR);
|
|
24827
26162
|
}
|
|
24828
|
-
async function readSourceArray(flow, flowType, icmPlugin, wmPlugin) {
|
|
24829
|
-
const sourceValue = await readMemoryValue(flow.sourceKey, icmPlugin, wmPlugin);
|
|
24830
|
-
if (!Array.isArray(sourceValue)) {
|
|
24831
|
-
return {
|
|
24832
|
-
completed: false,
|
|
24833
|
-
error: `${flowType} sourceKey "${flow.sourceKey}" is not an array (got ${typeof sourceValue})`
|
|
24834
|
-
};
|
|
24835
|
-
}
|
|
24836
|
-
const maxIter = Math.min(sourceValue.length, flow.maxIterations ?? sourceValue.length, HARD_MAX_ITERATIONS);
|
|
24837
|
-
return { array: sourceValue, maxIter };
|
|
24838
|
-
}
|
|
24839
26163
|
function prepareSubRoutine(tasks, parentTaskName) {
|
|
24840
26164
|
const subRoutine = resolveSubRoutine(tasks, parentTaskName);
|
|
24841
26165
|
return {
|
|
@@ -24870,10 +26194,141 @@ async function withTimeout(promise, timeoutMs, label) {
|
|
|
24870
26194
|
clearTimeout(timer);
|
|
24871
26195
|
}
|
|
24872
26196
|
}
|
|
24873
|
-
|
|
26197
|
+
var COMMON_ARRAY_FIELDS = ["data", "items", "results", "entries", "list", "records", "values", "elements"];
|
|
26198
|
+
function extractByPath(value, path6) {
|
|
26199
|
+
let current = value;
|
|
26200
|
+
if (typeof current === "string") {
|
|
26201
|
+
try {
|
|
26202
|
+
current = JSON.parse(current);
|
|
26203
|
+
} catch {
|
|
26204
|
+
return void 0;
|
|
26205
|
+
}
|
|
26206
|
+
}
|
|
26207
|
+
const segments = path6.replace(/\[(\d+)\]/g, ".$1").split(".").filter(Boolean);
|
|
26208
|
+
for (const segment of segments) {
|
|
26209
|
+
if (current == null || typeof current !== "object") return void 0;
|
|
26210
|
+
current = current[segment];
|
|
26211
|
+
}
|
|
26212
|
+
return current;
|
|
26213
|
+
}
|
|
26214
|
+
function tryCoerceToArray(value) {
|
|
26215
|
+
if (value === void 0 || value === null) return value;
|
|
26216
|
+
if (Array.isArray(value)) return value;
|
|
26217
|
+
if (typeof value === "string") {
|
|
26218
|
+
try {
|
|
26219
|
+
const parsed = JSON.parse(value);
|
|
26220
|
+
if (Array.isArray(parsed)) return parsed;
|
|
26221
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
26222
|
+
value = parsed;
|
|
26223
|
+
} else {
|
|
26224
|
+
return value;
|
|
26225
|
+
}
|
|
26226
|
+
} catch {
|
|
26227
|
+
return value;
|
|
26228
|
+
}
|
|
26229
|
+
}
|
|
26230
|
+
if (typeof value === "object" && value !== null) {
|
|
26231
|
+
for (const field of COMMON_ARRAY_FIELDS) {
|
|
26232
|
+
const candidate = value[field];
|
|
26233
|
+
if (Array.isArray(candidate)) return candidate;
|
|
26234
|
+
}
|
|
26235
|
+
}
|
|
26236
|
+
return value;
|
|
26237
|
+
}
|
|
26238
|
+
async function llmExtractArray(agent, rawValue) {
|
|
26239
|
+
const serialized = typeof rawValue === "string" ? rawValue : JSON.stringify(rawValue);
|
|
26240
|
+
const truncated = serialized.length > 8e3 ? serialized.slice(0, 8e3) + "\n...(truncated)" : serialized;
|
|
26241
|
+
const response = await agent.runDirect(
|
|
26242
|
+
[
|
|
26243
|
+
"Extract a JSON array from the data below for iteration.",
|
|
26244
|
+
"Return ONLY a valid JSON array. No explanation, no markdown fences, no extra text.",
|
|
26245
|
+
"If the data contains a list in any format (JSON, markdown, numbered list, comma-separated), convert it to a JSON array of items.",
|
|
26246
|
+
"",
|
|
26247
|
+
"Data:",
|
|
26248
|
+
truncated
|
|
26249
|
+
].join("\n"),
|
|
26250
|
+
{ temperature: 0, maxOutputTokens: 4096 }
|
|
26251
|
+
);
|
|
26252
|
+
const text = response.output_text?.trim() ?? "";
|
|
26253
|
+
const cleaned = text.replace(/^```(?:json|JSON)?\s*\n?/, "").replace(/\n?\s*```$/, "").trim();
|
|
26254
|
+
let parsed;
|
|
26255
|
+
try {
|
|
26256
|
+
parsed = JSON.parse(cleaned);
|
|
26257
|
+
} catch (parseErr) {
|
|
26258
|
+
throw new Error(`LLM returned invalid JSON: ${parseErr.message}. Raw: "${text.slice(0, 200)}"`);
|
|
26259
|
+
}
|
|
26260
|
+
if (!Array.isArray(parsed)) {
|
|
26261
|
+
throw new Error(`LLM extraction produced ${typeof parsed}, expected array`);
|
|
26262
|
+
}
|
|
26263
|
+
return parsed;
|
|
26264
|
+
}
|
|
26265
|
+
async function resolveFlowSource(flow, flowType, agent, execution, icmPlugin, wmPlugin) {
|
|
26266
|
+
const log = exports.logger.child({ fn: "resolveFlowSource", flowType });
|
|
26267
|
+
const source = flow.source;
|
|
26268
|
+
const lookups = [];
|
|
26269
|
+
if (typeof source === "string") {
|
|
26270
|
+
lookups.push({ key: source, label: `key "${source}"` });
|
|
26271
|
+
} else if (source.task) {
|
|
26272
|
+
lookups.push({
|
|
26273
|
+
key: `${ROUTINE_KEYS.TASK_OUTPUT_PREFIX}${source.task}`,
|
|
26274
|
+
path: source.path,
|
|
26275
|
+
label: `task output "${source.task}"`
|
|
26276
|
+
});
|
|
26277
|
+
if (execution) {
|
|
26278
|
+
const depTask = execution.plan.tasks.find((t) => t.name === source.task);
|
|
26279
|
+
if (depTask) {
|
|
26280
|
+
lookups.push({
|
|
26281
|
+
key: `${ROUTINE_KEYS.DEP_RESULT_PREFIX}${depTask.id}`,
|
|
26282
|
+
path: source.path,
|
|
26283
|
+
label: `dep result "${source.task}"`
|
|
26284
|
+
});
|
|
26285
|
+
}
|
|
26286
|
+
}
|
|
26287
|
+
} else if (source.key) {
|
|
26288
|
+
lookups.push({ key: source.key, path: source.path, label: `key "${source.key}"` });
|
|
26289
|
+
}
|
|
26290
|
+
if (lookups.length === 0) {
|
|
26291
|
+
return { completed: false, error: `${flowType}: source has no task, key, or string value` };
|
|
26292
|
+
}
|
|
26293
|
+
let rawValue;
|
|
26294
|
+
let resolvedVia;
|
|
26295
|
+
for (const { key, path: path6, label } of lookups) {
|
|
26296
|
+
const value2 = await readMemoryValue(key, icmPlugin, wmPlugin);
|
|
26297
|
+
if (value2 !== void 0) {
|
|
26298
|
+
rawValue = path6 ? extractByPath(value2, path6) : value2;
|
|
26299
|
+
resolvedVia = label;
|
|
26300
|
+
break;
|
|
26301
|
+
}
|
|
26302
|
+
}
|
|
26303
|
+
if (rawValue === void 0) {
|
|
26304
|
+
const tried = lookups.map((l) => l.label).join(", ");
|
|
26305
|
+
return { completed: false, error: `${flowType}: source not found (tried: ${tried})` };
|
|
26306
|
+
}
|
|
26307
|
+
log.debug({ resolvedVia }, "Source value found");
|
|
26308
|
+
let value = tryCoerceToArray(rawValue);
|
|
26309
|
+
if (!Array.isArray(value)) {
|
|
26310
|
+
log.info({ resolvedVia, valueType: typeof value }, "Source not an array, attempting LLM extraction");
|
|
26311
|
+
try {
|
|
26312
|
+
value = await llmExtractArray(agent, rawValue);
|
|
26313
|
+
log.info({ extractedLength: value.length }, "LLM extraction succeeded");
|
|
26314
|
+
} catch (err) {
|
|
26315
|
+
return {
|
|
26316
|
+
completed: false,
|
|
26317
|
+
error: `${flowType}: source value is not an array and LLM extraction failed: ${err.message}`
|
|
26318
|
+
};
|
|
26319
|
+
}
|
|
26320
|
+
}
|
|
26321
|
+
const arr = value;
|
|
26322
|
+
if (arr.length === 0) {
|
|
26323
|
+
log.warn("Source array is empty");
|
|
26324
|
+
}
|
|
26325
|
+
const maxIter = Math.min(arr.length, flow.maxIterations ?? arr.length, HARD_MAX_ITERATIONS);
|
|
26326
|
+
return { array: arr, maxIter };
|
|
26327
|
+
}
|
|
26328
|
+
async function handleMap(agent, flow, task, inputs, execution) {
|
|
24874
26329
|
const { icmPlugin, wmPlugin } = getPlugins(agent);
|
|
24875
26330
|
const log = exports.logger.child({ controlFlow: "map", task: task.name });
|
|
24876
|
-
const sourceResult = await
|
|
26331
|
+
const sourceResult = await resolveFlowSource(flow, "Map", agent, execution, icmPlugin, wmPlugin);
|
|
24877
26332
|
if ("completed" in sourceResult) return sourceResult;
|
|
24878
26333
|
const { array: array3, maxIter } = sourceResult;
|
|
24879
26334
|
const results = [];
|
|
@@ -24911,10 +26366,10 @@ async function handleMap(agent, flow, task, inputs) {
|
|
|
24911
26366
|
log.info({ resultCount: results.length }, "Map completed");
|
|
24912
26367
|
return { completed: true, result: results };
|
|
24913
26368
|
}
|
|
24914
|
-
async function handleFold(agent, flow, task, inputs) {
|
|
26369
|
+
async function handleFold(agent, flow, task, inputs, execution) {
|
|
24915
26370
|
const { icmPlugin, wmPlugin } = getPlugins(agent);
|
|
24916
26371
|
const log = exports.logger.child({ controlFlow: "fold", task: task.name });
|
|
24917
|
-
const sourceResult = await
|
|
26372
|
+
const sourceResult = await resolveFlowSource(flow, "Fold", agent, execution, icmPlugin, wmPlugin);
|
|
24918
26373
|
if ("completed" in sourceResult) return sourceResult;
|
|
24919
26374
|
const { array: array3, maxIter } = sourceResult;
|
|
24920
26375
|
let accumulator = flow.initialValue;
|
|
@@ -25005,13 +26460,13 @@ async function handleUntil(agent, flow, task, inputs) {
|
|
|
25005
26460
|
}
|
|
25006
26461
|
return { completed: false, error: `Until loop: maxIterations (${flow.maxIterations}) exceeded` };
|
|
25007
26462
|
}
|
|
25008
|
-
async function executeControlFlow(agent, task, inputs) {
|
|
26463
|
+
async function executeControlFlow(agent, task, inputs, execution) {
|
|
25009
26464
|
const flow = task.controlFlow;
|
|
25010
26465
|
switch (flow.type) {
|
|
25011
26466
|
case "map":
|
|
25012
|
-
return handleMap(agent, flow, task, inputs);
|
|
26467
|
+
return handleMap(agent, flow, task, inputs, execution);
|
|
25013
26468
|
case "fold":
|
|
25014
|
-
return handleFold(agent, flow, task, inputs);
|
|
26469
|
+
return handleFold(agent, flow, task, inputs, execution);
|
|
25015
26470
|
case "until":
|
|
25016
26471
|
return handleUntil(agent, flow, task, inputs);
|
|
25017
26472
|
default:
|
|
@@ -25040,7 +26495,26 @@ function defaultSystemPrompt(definition) {
|
|
|
25040
26495
|
);
|
|
25041
26496
|
return parts.join("\n");
|
|
25042
26497
|
}
|
|
25043
|
-
function
|
|
26498
|
+
function getOutputContracts(execution, currentTask) {
|
|
26499
|
+
const contracts = [];
|
|
26500
|
+
for (const task of execution.plan.tasks) {
|
|
26501
|
+
if (task.status !== "pending" || !task.controlFlow) continue;
|
|
26502
|
+
const flow = task.controlFlow;
|
|
26503
|
+
if (flow.type === "until") continue;
|
|
26504
|
+
const source = flow.source;
|
|
26505
|
+
const sourceTaskName = typeof source === "string" ? void 0 : source.task;
|
|
26506
|
+
if (sourceTaskName === currentTask.name) {
|
|
26507
|
+
contracts.push({
|
|
26508
|
+
storageKey: `${ROUTINE_KEYS.TASK_OUTPUT_PREFIX}${currentTask.name}`,
|
|
26509
|
+
format: "array",
|
|
26510
|
+
consumingTaskName: task.name,
|
|
26511
|
+
flowType: flow.type
|
|
26512
|
+
});
|
|
26513
|
+
}
|
|
26514
|
+
}
|
|
26515
|
+
return contracts;
|
|
26516
|
+
}
|
|
26517
|
+
function defaultTaskPrompt(task, execution) {
|
|
25044
26518
|
const parts = [];
|
|
25045
26519
|
parts.push(`## Current Task: ${task.name}`, "");
|
|
25046
26520
|
parts.push(task.description, "");
|
|
@@ -25065,6 +26539,21 @@ function defaultTaskPrompt(task) {
|
|
|
25065
26539
|
parts.push("Review the plan overview and dependency results before starting.");
|
|
25066
26540
|
parts.push("");
|
|
25067
26541
|
}
|
|
26542
|
+
if (execution) {
|
|
26543
|
+
const contracts = getOutputContracts(execution, task);
|
|
26544
|
+
if (contracts.length > 0) {
|
|
26545
|
+
parts.push("### Output Storage");
|
|
26546
|
+
for (const contract of contracts) {
|
|
26547
|
+
parts.push(
|
|
26548
|
+
`A downstream task ("${contract.consumingTaskName}") will ${contract.flowType} over your results.`,
|
|
26549
|
+
`Store your result as a JSON ${contract.format} using:`,
|
|
26550
|
+
` context_set("${contract.storageKey}", <your JSON ${contract.format}>)`,
|
|
26551
|
+
"Each array element should represent one item to process independently.",
|
|
26552
|
+
""
|
|
26553
|
+
);
|
|
26554
|
+
}
|
|
26555
|
+
}
|
|
26556
|
+
}
|
|
25068
26557
|
parts.push("After completing the work, store key results in memory once, then respond with a text summary (no more tool calls).");
|
|
25069
26558
|
return parts.join("\n");
|
|
25070
26559
|
}
|
|
@@ -25221,8 +26710,8 @@ var DEP_CLEANUP_CONFIG = {
|
|
|
25221
26710
|
wmPrefixes: [ROUTINE_KEYS.DEP_RESULT_PREFIX, ROUTINE_KEYS.WM_DEP_FINDINGS_PREFIX]
|
|
25222
26711
|
};
|
|
25223
26712
|
var FULL_CLEANUP_CONFIG = {
|
|
25224
|
-
icmPrefixes: ["__routine_", ROUTINE_KEYS.DEP_RESULT_PREFIX, "__map_", "__fold_"],
|
|
25225
|
-
wmPrefixes: [ROUTINE_KEYS.DEP_RESULT_PREFIX, ROUTINE_KEYS.WM_DEP_FINDINGS_PREFIX]
|
|
26713
|
+
icmPrefixes: ["__routine_", ROUTINE_KEYS.DEP_RESULT_PREFIX, "__map_", "__fold_", ROUTINE_KEYS.TASK_OUTPUT_PREFIX],
|
|
26714
|
+
wmPrefixes: [ROUTINE_KEYS.DEP_RESULT_PREFIX, ROUTINE_KEYS.WM_DEP_FINDINGS_PREFIX, ROUTINE_KEYS.TASK_OUTPUT_PREFIX]
|
|
25226
26715
|
};
|
|
25227
26716
|
async function injectRoutineContext(agent, execution, definition, currentTask) {
|
|
25228
26717
|
const { icmPlugin, wmPlugin } = getPlugins(agent);
|
|
@@ -25333,7 +26822,8 @@ async function executeRoutine(options) {
|
|
|
25333
26822
|
execution.startedAt = Date.now();
|
|
25334
26823
|
execution.lastUpdatedAt = Date.now();
|
|
25335
26824
|
const buildSystemPrompt = prompts?.system ?? defaultSystemPrompt;
|
|
25336
|
-
const
|
|
26825
|
+
const userTaskPromptBuilder = prompts?.task ?? defaultTaskPrompt;
|
|
26826
|
+
const buildTaskPrompt = (task) => userTaskPromptBuilder(task, execution);
|
|
25337
26827
|
const buildValidationPrompt = prompts?.validation ?? defaultValidationPrompt;
|
|
25338
26828
|
let agent;
|
|
25339
26829
|
const registeredHooks = [];
|
|
@@ -25424,7 +26914,7 @@ async function executeRoutine(options) {
|
|
|
25424
26914
|
const { icmPlugin } = getPlugins(agent);
|
|
25425
26915
|
if (getTask().controlFlow) {
|
|
25426
26916
|
try {
|
|
25427
|
-
const cfResult = await executeControlFlow(agent, getTask(), resolvedInputs);
|
|
26917
|
+
const cfResult = await executeControlFlow(agent, getTask(), resolvedInputs, execution);
|
|
25428
26918
|
if (cfResult.completed) {
|
|
25429
26919
|
execution.plan.tasks[taskIndex] = updateTaskStatus(getTask(), "completed");
|
|
25430
26920
|
execution.plan.tasks[taskIndex].result = { success: true, output: cfResult.result };
|
|
@@ -25574,6 +27064,261 @@ async function executeRoutine(options) {
|
|
|
25574
27064
|
}
|
|
25575
27065
|
}
|
|
25576
27066
|
|
|
27067
|
+
// src/core/createExecutionRecorder.ts
|
|
27068
|
+
init_Logger();
|
|
27069
|
+
function truncate(value, maxLen) {
|
|
27070
|
+
const str = typeof value === "string" ? value : JSON.stringify(value);
|
|
27071
|
+
if (!str) return "";
|
|
27072
|
+
return str.length > maxLen ? str.slice(0, maxLen) + "...(truncated)" : str;
|
|
27073
|
+
}
|
|
27074
|
+
function safeCall(fn, prefix) {
|
|
27075
|
+
try {
|
|
27076
|
+
const result = fn();
|
|
27077
|
+
if (result && typeof result.catch === "function") {
|
|
27078
|
+
result.catch((err) => {
|
|
27079
|
+
exports.logger.debug({ error: err.message }, `${prefix} async error`);
|
|
27080
|
+
});
|
|
27081
|
+
}
|
|
27082
|
+
} catch (err) {
|
|
27083
|
+
exports.logger.debug({ error: err.message }, `${prefix} sync error`);
|
|
27084
|
+
}
|
|
27085
|
+
}
|
|
27086
|
+
function createExecutionRecorder(options) {
|
|
27087
|
+
const { storage, executionId, logPrefix = "[Recorder]", maxTruncateLength = 500 } = options;
|
|
27088
|
+
const log = exports.logger.child({ executionId });
|
|
27089
|
+
let currentTaskName = "(unknown)";
|
|
27090
|
+
function pushStep(step) {
|
|
27091
|
+
safeCall(() => storage.pushStep(executionId, step), `${logPrefix} pushStep`);
|
|
27092
|
+
}
|
|
27093
|
+
function heartbeat() {
|
|
27094
|
+
safeCall(
|
|
27095
|
+
() => storage.update(executionId, { lastActivityAt: Date.now() }),
|
|
27096
|
+
`${logPrefix} heartbeat`
|
|
27097
|
+
);
|
|
27098
|
+
}
|
|
27099
|
+
const hooks = {
|
|
27100
|
+
"before:llm": (ctx) => {
|
|
27101
|
+
pushStep({
|
|
27102
|
+
timestamp: Date.now(),
|
|
27103
|
+
taskName: currentTaskName,
|
|
27104
|
+
type: "llm.start",
|
|
27105
|
+
data: { iteration: ctx.iteration }
|
|
27106
|
+
});
|
|
27107
|
+
return {};
|
|
27108
|
+
},
|
|
27109
|
+
"after:llm": (ctx) => {
|
|
27110
|
+
pushStep({
|
|
27111
|
+
timestamp: Date.now(),
|
|
27112
|
+
taskName: currentTaskName,
|
|
27113
|
+
type: "llm.complete",
|
|
27114
|
+
data: {
|
|
27115
|
+
iteration: ctx.iteration,
|
|
27116
|
+
duration: ctx.duration,
|
|
27117
|
+
tokens: ctx.response?.usage
|
|
27118
|
+
}
|
|
27119
|
+
});
|
|
27120
|
+
return {};
|
|
27121
|
+
},
|
|
27122
|
+
"before:tool": (ctx) => {
|
|
27123
|
+
pushStep({
|
|
27124
|
+
timestamp: Date.now(),
|
|
27125
|
+
taskName: currentTaskName,
|
|
27126
|
+
type: "tool.start",
|
|
27127
|
+
data: {
|
|
27128
|
+
toolName: ctx.toolCall.function.name,
|
|
27129
|
+
args: truncate(ctx.toolCall.function.arguments, maxTruncateLength)
|
|
27130
|
+
}
|
|
27131
|
+
});
|
|
27132
|
+
return {};
|
|
27133
|
+
},
|
|
27134
|
+
"after:tool": (ctx) => {
|
|
27135
|
+
pushStep({
|
|
27136
|
+
timestamp: Date.now(),
|
|
27137
|
+
taskName: currentTaskName,
|
|
27138
|
+
type: "tool.call",
|
|
27139
|
+
data: {
|
|
27140
|
+
toolName: ctx.toolCall.function.name,
|
|
27141
|
+
args: truncate(ctx.toolCall.function.arguments, maxTruncateLength),
|
|
27142
|
+
result: truncate(ctx.result?.content, maxTruncateLength),
|
|
27143
|
+
error: ctx.result?.error ? true : void 0
|
|
27144
|
+
}
|
|
27145
|
+
});
|
|
27146
|
+
return {};
|
|
27147
|
+
},
|
|
27148
|
+
"after:execution": () => {
|
|
27149
|
+
pushStep({
|
|
27150
|
+
timestamp: Date.now(),
|
|
27151
|
+
taskName: currentTaskName,
|
|
27152
|
+
type: "iteration.complete"
|
|
27153
|
+
});
|
|
27154
|
+
},
|
|
27155
|
+
"pause:check": () => {
|
|
27156
|
+
heartbeat();
|
|
27157
|
+
return { shouldPause: false };
|
|
27158
|
+
}
|
|
27159
|
+
};
|
|
27160
|
+
const onTaskStarted = (task, execution) => {
|
|
27161
|
+
currentTaskName = task.name;
|
|
27162
|
+
const now = Date.now();
|
|
27163
|
+
safeCall(
|
|
27164
|
+
() => storage.updateTask(executionId, task.name, {
|
|
27165
|
+
status: "in_progress",
|
|
27166
|
+
startedAt: now,
|
|
27167
|
+
attempts: task.attempts
|
|
27168
|
+
}),
|
|
27169
|
+
`${logPrefix} onTaskStarted`
|
|
27170
|
+
);
|
|
27171
|
+
pushStep({
|
|
27172
|
+
timestamp: now,
|
|
27173
|
+
taskName: task.name,
|
|
27174
|
+
type: "task.started",
|
|
27175
|
+
data: { taskId: task.id }
|
|
27176
|
+
});
|
|
27177
|
+
if (task.controlFlow) {
|
|
27178
|
+
pushStep({
|
|
27179
|
+
timestamp: now,
|
|
27180
|
+
taskName: task.name,
|
|
27181
|
+
type: "control_flow.started",
|
|
27182
|
+
data: { flowType: task.controlFlow.type }
|
|
27183
|
+
});
|
|
27184
|
+
}
|
|
27185
|
+
safeCall(
|
|
27186
|
+
() => storage.update(executionId, {
|
|
27187
|
+
progress: execution.progress,
|
|
27188
|
+
lastActivityAt: now
|
|
27189
|
+
}),
|
|
27190
|
+
`${logPrefix} onTaskStarted progress`
|
|
27191
|
+
);
|
|
27192
|
+
};
|
|
27193
|
+
const onTaskComplete = (task, execution) => {
|
|
27194
|
+
const now = Date.now();
|
|
27195
|
+
safeCall(
|
|
27196
|
+
() => storage.updateTask(executionId, task.name, {
|
|
27197
|
+
status: "completed",
|
|
27198
|
+
completedAt: now,
|
|
27199
|
+
attempts: task.attempts,
|
|
27200
|
+
result: task.result ? {
|
|
27201
|
+
success: true,
|
|
27202
|
+
output: truncate(task.result.output, maxTruncateLength),
|
|
27203
|
+
validationScore: task.result.validationScore,
|
|
27204
|
+
validationExplanation: task.result.validationExplanation
|
|
27205
|
+
} : void 0
|
|
27206
|
+
}),
|
|
27207
|
+
`${logPrefix} onTaskComplete`
|
|
27208
|
+
);
|
|
27209
|
+
pushStep({
|
|
27210
|
+
timestamp: now,
|
|
27211
|
+
taskName: task.name,
|
|
27212
|
+
type: "task.completed",
|
|
27213
|
+
data: {
|
|
27214
|
+
taskId: task.id,
|
|
27215
|
+
validationScore: task.result?.validationScore
|
|
27216
|
+
}
|
|
27217
|
+
});
|
|
27218
|
+
if (task.controlFlow) {
|
|
27219
|
+
pushStep({
|
|
27220
|
+
timestamp: now,
|
|
27221
|
+
taskName: task.name,
|
|
27222
|
+
type: "control_flow.completed",
|
|
27223
|
+
data: { flowType: task.controlFlow.type }
|
|
27224
|
+
});
|
|
27225
|
+
}
|
|
27226
|
+
safeCall(
|
|
27227
|
+
() => storage.update(executionId, {
|
|
27228
|
+
progress: execution.progress,
|
|
27229
|
+
lastActivityAt: now
|
|
27230
|
+
}),
|
|
27231
|
+
`${logPrefix} onTaskComplete progress`
|
|
27232
|
+
);
|
|
27233
|
+
};
|
|
27234
|
+
const onTaskFailed = (task, execution) => {
|
|
27235
|
+
const now = Date.now();
|
|
27236
|
+
safeCall(
|
|
27237
|
+
() => storage.updateTask(executionId, task.name, {
|
|
27238
|
+
status: "failed",
|
|
27239
|
+
completedAt: now,
|
|
27240
|
+
attempts: task.attempts,
|
|
27241
|
+
result: task.result ? {
|
|
27242
|
+
success: false,
|
|
27243
|
+
error: task.result.error,
|
|
27244
|
+
validationScore: task.result.validationScore,
|
|
27245
|
+
validationExplanation: task.result.validationExplanation
|
|
27246
|
+
} : void 0
|
|
27247
|
+
}),
|
|
27248
|
+
`${logPrefix} onTaskFailed`
|
|
27249
|
+
);
|
|
27250
|
+
pushStep({
|
|
27251
|
+
timestamp: now,
|
|
27252
|
+
taskName: task.name,
|
|
27253
|
+
type: "task.failed",
|
|
27254
|
+
data: {
|
|
27255
|
+
taskId: task.id,
|
|
27256
|
+
error: task.result?.error,
|
|
27257
|
+
attempts: task.attempts
|
|
27258
|
+
}
|
|
27259
|
+
});
|
|
27260
|
+
safeCall(
|
|
27261
|
+
() => storage.update(executionId, {
|
|
27262
|
+
progress: execution.progress,
|
|
27263
|
+
lastActivityAt: now
|
|
27264
|
+
}),
|
|
27265
|
+
`${logPrefix} onTaskFailed progress`
|
|
27266
|
+
);
|
|
27267
|
+
};
|
|
27268
|
+
const onTaskValidation = (task, result, _execution) => {
|
|
27269
|
+
pushStep({
|
|
27270
|
+
timestamp: Date.now(),
|
|
27271
|
+
taskName: task.name,
|
|
27272
|
+
type: "task.validation",
|
|
27273
|
+
data: {
|
|
27274
|
+
taskId: task.id,
|
|
27275
|
+
isComplete: result.isComplete,
|
|
27276
|
+
completionScore: result.completionScore,
|
|
27277
|
+
explanation: truncate(result.explanation, maxTruncateLength)
|
|
27278
|
+
}
|
|
27279
|
+
});
|
|
27280
|
+
};
|
|
27281
|
+
const finalize = async (execution, error) => {
|
|
27282
|
+
const now = Date.now();
|
|
27283
|
+
try {
|
|
27284
|
+
if (error || !execution) {
|
|
27285
|
+
await storage.update(executionId, {
|
|
27286
|
+
status: "failed",
|
|
27287
|
+
error: error?.message ?? "Unknown error",
|
|
27288
|
+
completedAt: now,
|
|
27289
|
+
lastActivityAt: now
|
|
27290
|
+
});
|
|
27291
|
+
if (error) {
|
|
27292
|
+
await storage.pushStep(executionId, {
|
|
27293
|
+
timestamp: now,
|
|
27294
|
+
taskName: currentTaskName,
|
|
27295
|
+
type: "execution.error",
|
|
27296
|
+
data: { error: error.message }
|
|
27297
|
+
});
|
|
27298
|
+
}
|
|
27299
|
+
} else {
|
|
27300
|
+
await storage.update(executionId, {
|
|
27301
|
+
status: execution.status,
|
|
27302
|
+
progress: execution.progress,
|
|
27303
|
+
error: execution.error,
|
|
27304
|
+
completedAt: execution.completedAt ?? now,
|
|
27305
|
+
lastActivityAt: now
|
|
27306
|
+
});
|
|
27307
|
+
}
|
|
27308
|
+
} catch (err) {
|
|
27309
|
+
log.error({ error: err.message }, `${logPrefix} finalize error`);
|
|
27310
|
+
}
|
|
27311
|
+
};
|
|
27312
|
+
return {
|
|
27313
|
+
hooks,
|
|
27314
|
+
onTaskStarted,
|
|
27315
|
+
onTaskComplete,
|
|
27316
|
+
onTaskFailed,
|
|
27317
|
+
onTaskValidation,
|
|
27318
|
+
finalize
|
|
27319
|
+
};
|
|
27320
|
+
}
|
|
27321
|
+
|
|
25577
27322
|
// src/core/index.ts
|
|
25578
27323
|
init_constants();
|
|
25579
27324
|
(class {
|
|
@@ -38898,6 +40643,38 @@ var InMemoryHistoryStorage = class {
|
|
|
38898
40643
|
this.summaries = state.summaries ? [...state.summaries] : [];
|
|
38899
40644
|
}
|
|
38900
40645
|
};
|
|
40646
|
+
|
|
40647
|
+
// src/domain/entities/RoutineExecutionRecord.ts
|
|
40648
|
+
function createTaskSnapshots(definition) {
|
|
40649
|
+
return definition.tasks.map((task) => ({
|
|
40650
|
+
taskId: task.id ?? task.name,
|
|
40651
|
+
name: task.name,
|
|
40652
|
+
description: task.description,
|
|
40653
|
+
status: "pending",
|
|
40654
|
+
attempts: 0,
|
|
40655
|
+
maxAttempts: task.maxAttempts ?? 3,
|
|
40656
|
+
controlFlowType: task.controlFlow?.type
|
|
40657
|
+
}));
|
|
40658
|
+
}
|
|
40659
|
+
function createRoutineExecutionRecord(definition, connectorName, model, trigger) {
|
|
40660
|
+
const now = Date.now();
|
|
40661
|
+
const executionId = `rexec-${now}-${Math.random().toString(36).substr(2, 9)}`;
|
|
40662
|
+
return {
|
|
40663
|
+
executionId,
|
|
40664
|
+
routineId: definition.id,
|
|
40665
|
+
routineName: definition.name,
|
|
40666
|
+
status: "running",
|
|
40667
|
+
progress: 0,
|
|
40668
|
+
tasks: createTaskSnapshots(definition),
|
|
40669
|
+
steps: [],
|
|
40670
|
+
taskCount: definition.tasks.length,
|
|
40671
|
+
connectorName,
|
|
40672
|
+
model,
|
|
40673
|
+
startedAt: now,
|
|
40674
|
+
lastActivityAt: now,
|
|
40675
|
+
trigger: trigger ?? { type: "manual" }
|
|
40676
|
+
};
|
|
40677
|
+
}
|
|
38901
40678
|
function getDefaultBaseDirectory3() {
|
|
38902
40679
|
const platform2 = process.platform;
|
|
38903
40680
|
if (platform2 === "win32") {
|
|
@@ -44741,6 +46518,7 @@ __export(tools_exports, {
|
|
|
44741
46518
|
createEditMeetingTool: () => createEditMeetingTool,
|
|
44742
46519
|
createExecuteJavaScriptTool: () => createExecuteJavaScriptTool,
|
|
44743
46520
|
createFindMeetingSlotsTool: () => createFindMeetingSlotsTool,
|
|
46521
|
+
createGenerateRoutine: () => createGenerateRoutine,
|
|
44744
46522
|
createGetMeetingTranscriptTool: () => createGetMeetingTranscriptTool,
|
|
44745
46523
|
createGetPRTool: () => createGetPRTool,
|
|
44746
46524
|
createGitHubReadFileTool: () => createGitHubReadFileTool,
|
|
@@ -44749,6 +46527,9 @@ __export(tools_exports, {
|
|
|
44749
46527
|
createImageGenerationTool: () => createImageGenerationTool,
|
|
44750
46528
|
createListDirectoryTool: () => createListDirectoryTool,
|
|
44751
46529
|
createMeetingTool: () => createMeetingTool,
|
|
46530
|
+
createMicrosoftListFilesTool: () => createMicrosoftListFilesTool,
|
|
46531
|
+
createMicrosoftReadFileTool: () => createMicrosoftReadFileTool,
|
|
46532
|
+
createMicrosoftSearchFilesTool: () => createMicrosoftSearchFilesTool,
|
|
44752
46533
|
createPRCommentsTool: () => createPRCommentsTool,
|
|
44753
46534
|
createPRFilesTool: () => createPRFilesTool,
|
|
44754
46535
|
createReadFileTool: () => createReadFileTool,
|
|
@@ -44781,14 +46562,18 @@ __export(tools_exports, {
|
|
|
44781
46562
|
desktopWindowList: () => desktopWindowList,
|
|
44782
46563
|
developerTools: () => developerTools,
|
|
44783
46564
|
editFile: () => editFile,
|
|
46565
|
+
encodeSharingUrl: () => encodeSharingUrl,
|
|
44784
46566
|
executeInVM: () => executeInVM,
|
|
44785
46567
|
executeJavaScript: () => executeJavaScript,
|
|
44786
46568
|
expandTilde: () => expandTilde,
|
|
44787
46569
|
formatAttendees: () => formatAttendees,
|
|
46570
|
+
formatFileSize: () => formatFileSize,
|
|
44788
46571
|
formatRecipients: () => formatRecipients,
|
|
46572
|
+
generateRoutine: () => generateRoutine,
|
|
44789
46573
|
getAllBuiltInTools: () => getAllBuiltInTools,
|
|
44790
46574
|
getBackgroundOutput: () => getBackgroundOutput,
|
|
44791
46575
|
getDesktopDriver: () => getDesktopDriver,
|
|
46576
|
+
getDrivePrefix: () => getDrivePrefix,
|
|
44792
46577
|
getMediaOutputHandler: () => getMediaOutputHandler,
|
|
44793
46578
|
getMediaStorage: () => getMediaStorage,
|
|
44794
46579
|
getToolByName: () => getToolByName,
|
|
@@ -44802,7 +46587,9 @@ __export(tools_exports, {
|
|
|
44802
46587
|
hydrateCustomTool: () => hydrateCustomTool,
|
|
44803
46588
|
isBlockedCommand: () => isBlockedCommand,
|
|
44804
46589
|
isExcludedExtension: () => isExcludedExtension,
|
|
46590
|
+
isMicrosoftFileUrl: () => isMicrosoftFileUrl,
|
|
44805
46591
|
isTeamsMeetingUrl: () => isTeamsMeetingUrl,
|
|
46592
|
+
isWebUrl: () => isWebUrl,
|
|
44806
46593
|
jsonManipulator: () => jsonManipulator,
|
|
44807
46594
|
killBackgroundProcess: () => killBackgroundProcess,
|
|
44808
46595
|
listDirectory: () => listDirectory,
|
|
@@ -44813,6 +46600,7 @@ __export(tools_exports, {
|
|
|
44813
46600
|
parseRepository: () => parseRepository,
|
|
44814
46601
|
readFile: () => readFile5,
|
|
44815
46602
|
resetDefaultDriver: () => resetDefaultDriver,
|
|
46603
|
+
resolveFileEndpoints: () => resolveFileEndpoints,
|
|
44816
46604
|
resolveMeetingId: () => resolveMeetingId,
|
|
44817
46605
|
resolveRepository: () => resolveRepository,
|
|
44818
46606
|
setMediaOutputHandler: () => setMediaOutputHandler,
|
|
@@ -49065,6 +50853,87 @@ async function resolveMeetingId(connector, input, prefix, effectiveUserId, effec
|
|
|
49065
50853
|
subject: meetings.value[0].subject
|
|
49066
50854
|
};
|
|
49067
50855
|
}
|
|
50856
|
+
var DEFAULT_MAX_FILE_SIZE_BYTES = 50 * 1024 * 1024;
|
|
50857
|
+
var DEFAULT_FILE_SIZE_LIMITS = {
|
|
50858
|
+
".pptx": 100 * 1024 * 1024,
|
|
50859
|
+
// 100 MB — presentations are image-heavy
|
|
50860
|
+
".ppt": 100 * 1024 * 1024,
|
|
50861
|
+
".odp": 100 * 1024 * 1024
|
|
50862
|
+
};
|
|
50863
|
+
function getFileSizeLimit(ext, overrides, defaultLimit) {
|
|
50864
|
+
const merged = { ...DEFAULT_FILE_SIZE_LIMITS, ...overrides };
|
|
50865
|
+
return merged[ext.toLowerCase()] ?? defaultLimit ?? DEFAULT_MAX_FILE_SIZE_BYTES;
|
|
50866
|
+
}
|
|
50867
|
+
var SUPPORTED_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
50868
|
+
".docx",
|
|
50869
|
+
".pptx",
|
|
50870
|
+
".xlsx",
|
|
50871
|
+
".csv",
|
|
50872
|
+
".pdf",
|
|
50873
|
+
".odt",
|
|
50874
|
+
".odp",
|
|
50875
|
+
".ods",
|
|
50876
|
+
".rtf",
|
|
50877
|
+
".html",
|
|
50878
|
+
".htm",
|
|
50879
|
+
".txt",
|
|
50880
|
+
".md",
|
|
50881
|
+
".json",
|
|
50882
|
+
".xml",
|
|
50883
|
+
".yaml",
|
|
50884
|
+
".yml"
|
|
50885
|
+
]);
|
|
50886
|
+
function encodeSharingUrl(webUrl) {
|
|
50887
|
+
const base64 = Buffer.from(webUrl, "utf-8").toString("base64").replace(/=+$/, "").replace(/\//g, "_").replace(/\+/g, "-");
|
|
50888
|
+
return `u!${base64}`;
|
|
50889
|
+
}
|
|
50890
|
+
function isWebUrl(source) {
|
|
50891
|
+
return /^https?:\/\//i.test(source.trim());
|
|
50892
|
+
}
|
|
50893
|
+
function isMicrosoftFileUrl(source) {
|
|
50894
|
+
try {
|
|
50895
|
+
const url2 = new URL(source.trim());
|
|
50896
|
+
const host = url2.hostname.toLowerCase();
|
|
50897
|
+
return host.endsWith(".sharepoint.com") || host.endsWith(".sharepoint-df.com") || host === "onedrive.live.com" || host === "1drv.ms";
|
|
50898
|
+
} catch {
|
|
50899
|
+
return false;
|
|
50900
|
+
}
|
|
50901
|
+
}
|
|
50902
|
+
function getDrivePrefix(userPrefix, options) {
|
|
50903
|
+
if (options?.siteId) return `/sites/${options.siteId}/drive`;
|
|
50904
|
+
if (options?.driveId) return `/drives/${options.driveId}`;
|
|
50905
|
+
return `${userPrefix}/drive`;
|
|
50906
|
+
}
|
|
50907
|
+
function resolveFileEndpoints(source, drivePrefix) {
|
|
50908
|
+
const trimmed = source.trim();
|
|
50909
|
+
if (isWebUrl(trimmed)) {
|
|
50910
|
+
const token = encodeSharingUrl(trimmed);
|
|
50911
|
+
return {
|
|
50912
|
+
metadataEndpoint: `/shares/${token}/driveItem`,
|
|
50913
|
+
contentEndpoint: `/shares/${token}/driveItem/content`,
|
|
50914
|
+
isSharedUrl: true
|
|
50915
|
+
};
|
|
50916
|
+
}
|
|
50917
|
+
if (trimmed.startsWith("/")) {
|
|
50918
|
+
const path6 = trimmed.endsWith("/") ? trimmed.slice(0, -1) : trimmed;
|
|
50919
|
+
return {
|
|
50920
|
+
metadataEndpoint: `${drivePrefix}/root:${path6}:`,
|
|
50921
|
+
contentEndpoint: `${drivePrefix}/root:${path6}:/content`,
|
|
50922
|
+
isSharedUrl: false
|
|
50923
|
+
};
|
|
50924
|
+
}
|
|
50925
|
+
return {
|
|
50926
|
+
metadataEndpoint: `${drivePrefix}/items/${trimmed}`,
|
|
50927
|
+
contentEndpoint: `${drivePrefix}/items/${trimmed}/content`,
|
|
50928
|
+
isSharedUrl: false
|
|
50929
|
+
};
|
|
50930
|
+
}
|
|
50931
|
+
function formatFileSize(bytes) {
|
|
50932
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
50933
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
50934
|
+
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
50935
|
+
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
|
|
50936
|
+
}
|
|
49068
50937
|
|
|
49069
50938
|
// src/tools/microsoft/createDraftEmail.ts
|
|
49070
50939
|
function createDraftEmailTool(connector, userId) {
|
|
@@ -49770,16 +51639,539 @@ EXAMPLES:
|
|
|
49770
51639
|
};
|
|
49771
51640
|
}
|
|
49772
51641
|
|
|
51642
|
+
// src/tools/microsoft/readFile.ts
|
|
51643
|
+
function createMicrosoftReadFileTool(connector, userId, config) {
|
|
51644
|
+
const reader = DocumentReader.create();
|
|
51645
|
+
const defaultLimit = config?.maxFileSizeBytes ?? DEFAULT_MAX_FILE_SIZE_BYTES;
|
|
51646
|
+
const sizeOverrides = config?.fileSizeLimits;
|
|
51647
|
+
return {
|
|
51648
|
+
definition: {
|
|
51649
|
+
type: "function",
|
|
51650
|
+
function: {
|
|
51651
|
+
name: "read_file",
|
|
51652
|
+
description: `Read a file from Microsoft OneDrive or SharePoint and return its content as **markdown text**.
|
|
51653
|
+
|
|
51654
|
+
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.
|
|
51655
|
+
|
|
51656
|
+
**Supported formats:** .docx, .pptx, .xlsx, .csv, .pdf, .odt, .odp, .ods, .rtf, .html, .txt, .md, .json, .xml, .yaml
|
|
51657
|
+
**Not supported:** .doc, .xls, .ppt (legacy Office formats), images, videos, executables.
|
|
51658
|
+
|
|
51659
|
+
**The \`source\` parameter is flexible \u2014 you can provide any of these:**
|
|
51660
|
+
- A SharePoint or OneDrive **web URL**: \`https://contoso.sharepoint.com/sites/team/Shared Documents/report.docx\`
|
|
51661
|
+
- A OneDrive **sharing link**: \`https://1drv.ms/w/s!AmVg...\`
|
|
51662
|
+
- A **file path** within the drive: \`/Documents/Q4 Report.docx\`
|
|
51663
|
+
- A Graph API **item ID**: \`01ABCDEF123456\`
|
|
51664
|
+
|
|
51665
|
+
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.
|
|
51666
|
+
|
|
51667
|
+
**Maximum file size:** 50 MB (100 MB for presentations). For larger files, use the search or list tools to find smaller alternatives.
|
|
51668
|
+
|
|
51669
|
+
**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.`,
|
|
51670
|
+
parameters: {
|
|
51671
|
+
type: "object",
|
|
51672
|
+
properties: {
|
|
51673
|
+
source: {
|
|
51674
|
+
type: "string",
|
|
51675
|
+
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.'
|
|
51676
|
+
},
|
|
51677
|
+
driveId: {
|
|
51678
|
+
type: "string",
|
|
51679
|
+
description: "Optional drive ID. Omit to use the default drive. Only needed when accessing a specific non-default drive."
|
|
51680
|
+
},
|
|
51681
|
+
siteId: {
|
|
51682
|
+
type: "string",
|
|
51683
|
+
description: "Optional SharePoint site ID. Use this instead of driveId to access files in a specific SharePoint site's default drive."
|
|
51684
|
+
},
|
|
51685
|
+
targetUser: {
|
|
51686
|
+
type: "string",
|
|
51687
|
+
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.`
|
|
51688
|
+
}
|
|
51689
|
+
},
|
|
51690
|
+
required: ["source"]
|
|
51691
|
+
}
|
|
51692
|
+
},
|
|
51693
|
+
blocking: true,
|
|
51694
|
+
timeout: 6e4
|
|
51695
|
+
},
|
|
51696
|
+
describeCall: (args) => {
|
|
51697
|
+
const src = args.source.length > 80 ? args.source.slice(0, 77) + "..." : args.source;
|
|
51698
|
+
return `Read: ${src}`;
|
|
51699
|
+
},
|
|
51700
|
+
permission: {
|
|
51701
|
+
scope: "session",
|
|
51702
|
+
riskLevel: "low",
|
|
51703
|
+
approvalMessage: `Read a file from OneDrive/SharePoint via ${connector.displayName}`
|
|
51704
|
+
},
|
|
51705
|
+
execute: async (args, context) => {
|
|
51706
|
+
const effectiveUserId = context?.userId ?? userId;
|
|
51707
|
+
const effectiveAccountId = context?.accountId;
|
|
51708
|
+
if (!args.source || !args.source.trim()) {
|
|
51709
|
+
return {
|
|
51710
|
+
success: false,
|
|
51711
|
+
error: 'The "source" parameter is required. Provide a file URL, path, or item ID.'
|
|
51712
|
+
};
|
|
51713
|
+
}
|
|
51714
|
+
if (isWebUrl(args.source) && !isMicrosoftFileUrl(args.source)) {
|
|
51715
|
+
return {
|
|
51716
|
+
success: false,
|
|
51717
|
+
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.`
|
|
51718
|
+
};
|
|
51719
|
+
}
|
|
51720
|
+
try {
|
|
51721
|
+
const userPrefix = getUserPathPrefix(connector, args.targetUser);
|
|
51722
|
+
const drivePrefix = getDrivePrefix(userPrefix, {
|
|
51723
|
+
siteId: args.siteId,
|
|
51724
|
+
driveId: args.driveId
|
|
51725
|
+
});
|
|
51726
|
+
const { metadataEndpoint, contentEndpoint, isSharedUrl } = resolveFileEndpoints(
|
|
51727
|
+
args.source,
|
|
51728
|
+
drivePrefix
|
|
51729
|
+
);
|
|
51730
|
+
const metadataQueryParams = isSharedUrl ? {} : { "$select": "id,name,size,file,folder,webUrl,parentReference" };
|
|
51731
|
+
const metadata = await microsoftFetch(connector, metadataEndpoint, {
|
|
51732
|
+
userId: effectiveUserId,
|
|
51733
|
+
accountId: effectiveAccountId,
|
|
51734
|
+
queryParams: metadataQueryParams
|
|
51735
|
+
});
|
|
51736
|
+
if (metadata.folder) {
|
|
51737
|
+
return {
|
|
51738
|
+
success: false,
|
|
51739
|
+
error: `"${metadata.name}" is a folder, not a file. Use the list_files tool to browse folder contents.`
|
|
51740
|
+
};
|
|
51741
|
+
}
|
|
51742
|
+
const ext = getExtension(metadata.name);
|
|
51743
|
+
const sizeLimit = getFileSizeLimit(ext, sizeOverrides, defaultLimit);
|
|
51744
|
+
if (metadata.size > sizeLimit) {
|
|
51745
|
+
return {
|
|
51746
|
+
success: false,
|
|
51747
|
+
filename: metadata.name,
|
|
51748
|
+
sizeBytes: metadata.size,
|
|
51749
|
+
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.`
|
|
51750
|
+
};
|
|
51751
|
+
}
|
|
51752
|
+
if (ext && !SUPPORTED_EXTENSIONS.has(ext) && !FormatDetector.isBinaryDocumentFormat(ext)) {
|
|
51753
|
+
return {
|
|
51754
|
+
success: false,
|
|
51755
|
+
filename: metadata.name,
|
|
51756
|
+
mimeType: metadata.file?.mimeType,
|
|
51757
|
+
error: `File format "${ext}" is not supported for text extraction. Supported formats: ${[...SUPPORTED_EXTENSIONS].join(", ")}`
|
|
51758
|
+
};
|
|
51759
|
+
}
|
|
51760
|
+
const response = await connector.fetch(
|
|
51761
|
+
contentEndpoint,
|
|
51762
|
+
{ method: "GET" },
|
|
51763
|
+
effectiveUserId,
|
|
51764
|
+
effectiveAccountId
|
|
51765
|
+
);
|
|
51766
|
+
if (!response.ok) {
|
|
51767
|
+
throw new MicrosoftAPIError(response.status, response.statusText, await response.text());
|
|
51768
|
+
}
|
|
51769
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
51770
|
+
const buffer = Buffer.from(arrayBuffer);
|
|
51771
|
+
const result = await reader.read(
|
|
51772
|
+
{ type: "buffer", buffer, filename: metadata.name },
|
|
51773
|
+
{ extractImages: false }
|
|
51774
|
+
);
|
|
51775
|
+
if (!result.success) {
|
|
51776
|
+
return {
|
|
51777
|
+
success: false,
|
|
51778
|
+
filename: metadata.name,
|
|
51779
|
+
error: `Failed to convert "${metadata.name}" to markdown: ${result.error ?? "unknown error"}`
|
|
51780
|
+
};
|
|
51781
|
+
}
|
|
51782
|
+
const markdown = mergeTextPieces(result.pieces);
|
|
51783
|
+
if (!markdown || markdown.trim().length === 0) {
|
|
51784
|
+
return {
|
|
51785
|
+
success: true,
|
|
51786
|
+
filename: metadata.name,
|
|
51787
|
+
sizeBytes: metadata.size,
|
|
51788
|
+
mimeType: metadata.file?.mimeType,
|
|
51789
|
+
webUrl: metadata.webUrl,
|
|
51790
|
+
markdown: "*(empty document \u2014 no text content found)*"
|
|
51791
|
+
};
|
|
51792
|
+
}
|
|
51793
|
+
return {
|
|
51794
|
+
success: true,
|
|
51795
|
+
filename: metadata.name,
|
|
51796
|
+
sizeBytes: metadata.size,
|
|
51797
|
+
mimeType: metadata.file?.mimeType,
|
|
51798
|
+
webUrl: metadata.webUrl,
|
|
51799
|
+
markdown
|
|
51800
|
+
};
|
|
51801
|
+
} catch (error) {
|
|
51802
|
+
if (error instanceof MicrosoftAPIError) {
|
|
51803
|
+
if (error.status === 404) {
|
|
51804
|
+
return {
|
|
51805
|
+
success: false,
|
|
51806
|
+
error: `File not found. Check that the source "${args.source}" is correct and you have access.`
|
|
51807
|
+
};
|
|
51808
|
+
}
|
|
51809
|
+
if (error.status === 403 || error.status === 401) {
|
|
51810
|
+
return {
|
|
51811
|
+
success: false,
|
|
51812
|
+
error: `Access denied. The connector may not have sufficient permissions (Files.Read or Sites.Read.All required).`
|
|
51813
|
+
};
|
|
51814
|
+
}
|
|
51815
|
+
}
|
|
51816
|
+
return {
|
|
51817
|
+
success: false,
|
|
51818
|
+
error: `Failed to read file: ${error instanceof Error ? error.message : String(error)}`
|
|
51819
|
+
};
|
|
51820
|
+
}
|
|
51821
|
+
}
|
|
51822
|
+
};
|
|
51823
|
+
}
|
|
51824
|
+
function getExtension(filename) {
|
|
51825
|
+
const dot = filename.lastIndexOf(".");
|
|
51826
|
+
if (dot < 0) return "";
|
|
51827
|
+
return filename.slice(dot).toLowerCase();
|
|
51828
|
+
}
|
|
51829
|
+
|
|
51830
|
+
// src/tools/microsoft/listFiles.ts
|
|
51831
|
+
var DEFAULT_LIMIT = 50;
|
|
51832
|
+
var MAX_LIMIT = 200;
|
|
51833
|
+
var SELECT_FIELDS = "id,name,size,lastModifiedDateTime,file,folder,webUrl";
|
|
51834
|
+
function createMicrosoftListFilesTool(connector, userId) {
|
|
51835
|
+
return {
|
|
51836
|
+
definition: {
|
|
51837
|
+
type: "function",
|
|
51838
|
+
function: {
|
|
51839
|
+
name: "list_files",
|
|
51840
|
+
description: `List files and folders in a Microsoft OneDrive or SharePoint directory.
|
|
51841
|
+
|
|
51842
|
+
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**.
|
|
51843
|
+
|
|
51844
|
+
**The \`path\` parameter is flexible \u2014 you can provide:**
|
|
51845
|
+
- A **folder path** within the drive: \`/Documents/Projects\` or \`/\` for root
|
|
51846
|
+
- A SharePoint/OneDrive **folder URL**: \`https://contoso.sharepoint.com/sites/team/Shared Documents/Projects\`
|
|
51847
|
+
- Omit entirely to list the root of the drive
|
|
51848
|
+
|
|
51849
|
+
**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).
|
|
51850
|
+
|
|
51851
|
+
**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.`,
|
|
51852
|
+
parameters: {
|
|
51853
|
+
type: "object",
|
|
51854
|
+
properties: {
|
|
51855
|
+
path: {
|
|
51856
|
+
type: "string",
|
|
51857
|
+
description: 'Folder path (e.g., "/Documents/Projects") or a web URL to a SharePoint/OneDrive folder. Omit to list root.'
|
|
51858
|
+
},
|
|
51859
|
+
driveId: {
|
|
51860
|
+
type: "string",
|
|
51861
|
+
description: "Optional drive ID for a specific non-default drive."
|
|
51862
|
+
},
|
|
51863
|
+
siteId: {
|
|
51864
|
+
type: "string",
|
|
51865
|
+
description: "Optional SharePoint site ID to access that site's default document library."
|
|
51866
|
+
},
|
|
51867
|
+
search: {
|
|
51868
|
+
type: "string",
|
|
51869
|
+
description: "Optional search query to filter results by filename or content. Searches within the specified folder and all subfolders."
|
|
51870
|
+
},
|
|
51871
|
+
limit: {
|
|
51872
|
+
type: "number",
|
|
51873
|
+
description: `Maximum number of items to return (default: ${DEFAULT_LIMIT}, max: ${MAX_LIMIT}).`
|
|
51874
|
+
},
|
|
51875
|
+
targetUser: {
|
|
51876
|
+
type: "string",
|
|
51877
|
+
description: "User ID or email. Only needed with application-only (client_credentials) auth."
|
|
51878
|
+
}
|
|
51879
|
+
}
|
|
51880
|
+
}
|
|
51881
|
+
},
|
|
51882
|
+
blocking: true,
|
|
51883
|
+
timeout: 3e4
|
|
51884
|
+
},
|
|
51885
|
+
describeCall: (args) => {
|
|
51886
|
+
const loc = args.path || "/";
|
|
51887
|
+
const suffix = args.search ? ` (search: "${args.search}")` : "";
|
|
51888
|
+
return `List: ${loc}${suffix}`;
|
|
51889
|
+
},
|
|
51890
|
+
permission: {
|
|
51891
|
+
scope: "session",
|
|
51892
|
+
riskLevel: "low",
|
|
51893
|
+
approvalMessage: `List files in OneDrive/SharePoint via ${connector.displayName}`
|
|
51894
|
+
},
|
|
51895
|
+
execute: async (args, context) => {
|
|
51896
|
+
const effectiveUserId = context?.userId ?? userId;
|
|
51897
|
+
const effectiveAccountId = context?.accountId;
|
|
51898
|
+
const limit = Math.min(Math.max(1, args.limit ?? DEFAULT_LIMIT), MAX_LIMIT);
|
|
51899
|
+
try {
|
|
51900
|
+
const userPrefix = getUserPathPrefix(connector, args.targetUser);
|
|
51901
|
+
const drivePrefix = getDrivePrefix(userPrefix, {
|
|
51902
|
+
siteId: args.siteId,
|
|
51903
|
+
driveId: args.driveId
|
|
51904
|
+
});
|
|
51905
|
+
let endpoint;
|
|
51906
|
+
if (args.search) {
|
|
51907
|
+
const folderBase = args.path && !isWebUrl(args.path) ? `${drivePrefix}/root:${normalizePath(args.path)}:` : drivePrefix;
|
|
51908
|
+
endpoint = `${folderBase}/search(q='${encodeSearchQuery(args.search)}')`;
|
|
51909
|
+
} else if (args.path) {
|
|
51910
|
+
if (isWebUrl(args.path)) {
|
|
51911
|
+
const token = encodeSharingUrl(args.path.trim());
|
|
51912
|
+
endpoint = `/shares/${token}/driveItem/children`;
|
|
51913
|
+
} else {
|
|
51914
|
+
const path6 = normalizePath(args.path);
|
|
51915
|
+
endpoint = path6 === "/" ? `${drivePrefix}/root/children` : `${drivePrefix}/root:${path6}:/children`;
|
|
51916
|
+
}
|
|
51917
|
+
} else {
|
|
51918
|
+
endpoint = `${drivePrefix}/root/children`;
|
|
51919
|
+
}
|
|
51920
|
+
const queryParams = {
|
|
51921
|
+
"$top": limit,
|
|
51922
|
+
"$select": SELECT_FIELDS
|
|
51923
|
+
};
|
|
51924
|
+
if (!args.search) {
|
|
51925
|
+
queryParams["$orderby"] = "name asc";
|
|
51926
|
+
}
|
|
51927
|
+
const data = await microsoftFetch(connector, endpoint, {
|
|
51928
|
+
userId: effectiveUserId,
|
|
51929
|
+
accountId: effectiveAccountId,
|
|
51930
|
+
queryParams
|
|
51931
|
+
});
|
|
51932
|
+
const items = (data.value || []).map(mapDriveItem);
|
|
51933
|
+
return {
|
|
51934
|
+
success: true,
|
|
51935
|
+
items,
|
|
51936
|
+
totalCount: items.length,
|
|
51937
|
+
hasMore: !!data["@odata.nextLink"]
|
|
51938
|
+
};
|
|
51939
|
+
} catch (error) {
|
|
51940
|
+
return {
|
|
51941
|
+
success: false,
|
|
51942
|
+
error: `Failed to list files: ${error instanceof Error ? error.message : String(error)}`
|
|
51943
|
+
};
|
|
51944
|
+
}
|
|
51945
|
+
}
|
|
51946
|
+
};
|
|
51947
|
+
}
|
|
51948
|
+
function mapDriveItem(item) {
|
|
51949
|
+
return {
|
|
51950
|
+
name: item.name,
|
|
51951
|
+
type: item.folder ? "folder" : "file",
|
|
51952
|
+
size: item.size,
|
|
51953
|
+
sizeFormatted: formatFileSize(item.size),
|
|
51954
|
+
mimeType: item.file?.mimeType,
|
|
51955
|
+
lastModified: item.lastModifiedDateTime,
|
|
51956
|
+
webUrl: item.webUrl,
|
|
51957
|
+
id: item.id,
|
|
51958
|
+
childCount: item.folder?.childCount
|
|
51959
|
+
};
|
|
51960
|
+
}
|
|
51961
|
+
function normalizePath(path6) {
|
|
51962
|
+
let p = path6.trim();
|
|
51963
|
+
if (!p.startsWith("/")) p = "/" + p;
|
|
51964
|
+
if (p.endsWith("/") && p.length > 1) p = p.slice(0, -1);
|
|
51965
|
+
return p;
|
|
51966
|
+
}
|
|
51967
|
+
function encodeSearchQuery(query) {
|
|
51968
|
+
return query.replace(/\\/g, "\\\\").replace(/'/g, "''");
|
|
51969
|
+
}
|
|
51970
|
+
|
|
51971
|
+
// src/tools/microsoft/searchFiles.ts
|
|
51972
|
+
var DEFAULT_LIMIT2 = 25;
|
|
51973
|
+
var MAX_LIMIT2 = 100;
|
|
51974
|
+
function createMicrosoftSearchFilesTool(connector, userId) {
|
|
51975
|
+
return {
|
|
51976
|
+
definition: {
|
|
51977
|
+
type: "function",
|
|
51978
|
+
function: {
|
|
51979
|
+
name: "search_files",
|
|
51980
|
+
description: `Search for files across Microsoft OneDrive and SharePoint.
|
|
51981
|
+
|
|
51982
|
+
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.
|
|
51983
|
+
|
|
51984
|
+
**Supports Microsoft's KQL (Keyword Query Language):**
|
|
51985
|
+
- Simple text: \`"quarterly report"\`
|
|
51986
|
+
- By filename: \`filename:budget.xlsx\`
|
|
51987
|
+
- By author: \`author:"Jane Smith"\`
|
|
51988
|
+
- By date: \`lastModifiedTime>2024-01-01\`
|
|
51989
|
+
- Combined: \`project proposal filetype:docx\`
|
|
51990
|
+
|
|
51991
|
+
**Filter by file type:** Use the \`fileTypes\` parameter (e.g., \`["docx", "pdf"]\`) to restrict results to specific formats.
|
|
51992
|
+
|
|
51993
|
+
**Limit to a SharePoint site:** Provide the \`siteId\` parameter to search only within a specific site.
|
|
51994
|
+
|
|
51995
|
+
**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.
|
|
51996
|
+
|
|
51997
|
+
**Tip:** Start with a broad search, then use read_file on the most relevant results.`,
|
|
51998
|
+
parameters: {
|
|
51999
|
+
type: "object",
|
|
52000
|
+
properties: {
|
|
52001
|
+
query: {
|
|
52002
|
+
type: "string",
|
|
52003
|
+
description: 'Search query. Supports plain text and KQL syntax (e.g., "budget report", "filename:spec.docx", "author:john filetype:pptx").'
|
|
52004
|
+
},
|
|
52005
|
+
siteId: {
|
|
52006
|
+
type: "string",
|
|
52007
|
+
description: "Optional SharePoint site ID to limit search to a specific site."
|
|
52008
|
+
},
|
|
52009
|
+
fileTypes: {
|
|
52010
|
+
type: "array",
|
|
52011
|
+
items: { type: "string" },
|
|
52012
|
+
description: 'Optional file type filter. Array of extensions without dots (e.g., ["docx", "pdf", "xlsx"]).'
|
|
52013
|
+
},
|
|
52014
|
+
limit: {
|
|
52015
|
+
type: "number",
|
|
52016
|
+
description: `Maximum number of results (default: ${DEFAULT_LIMIT2}, max: ${MAX_LIMIT2}).`
|
|
52017
|
+
},
|
|
52018
|
+
targetUser: {
|
|
52019
|
+
type: "string",
|
|
52020
|
+
description: "User ID or email. Only needed with application-only (client_credentials) auth."
|
|
52021
|
+
}
|
|
52022
|
+
},
|
|
52023
|
+
required: ["query"]
|
|
52024
|
+
}
|
|
52025
|
+
},
|
|
52026
|
+
blocking: true,
|
|
52027
|
+
timeout: 3e4
|
|
52028
|
+
},
|
|
52029
|
+
describeCall: (args) => {
|
|
52030
|
+
const types = args.fileTypes?.length ? ` [${args.fileTypes.join(",")}]` : "";
|
|
52031
|
+
return `Search: "${args.query}"${types}`;
|
|
52032
|
+
},
|
|
52033
|
+
permission: {
|
|
52034
|
+
scope: "session",
|
|
52035
|
+
riskLevel: "low",
|
|
52036
|
+
approvalMessage: `Search files in OneDrive/SharePoint via ${connector.displayName}`
|
|
52037
|
+
},
|
|
52038
|
+
execute: async (args, context) => {
|
|
52039
|
+
const effectiveUserId = context?.userId ?? userId;
|
|
52040
|
+
const effectiveAccountId = context?.accountId;
|
|
52041
|
+
const limit = Math.min(Math.max(1, args.limit ?? DEFAULT_LIMIT2), MAX_LIMIT2);
|
|
52042
|
+
try {
|
|
52043
|
+
if (args.siteId) {
|
|
52044
|
+
return await searchViaDriveEndpoint(
|
|
52045
|
+
connector,
|
|
52046
|
+
args,
|
|
52047
|
+
limit,
|
|
52048
|
+
effectiveUserId,
|
|
52049
|
+
effectiveAccountId
|
|
52050
|
+
);
|
|
52051
|
+
}
|
|
52052
|
+
let queryString = args.query;
|
|
52053
|
+
if (args.fileTypes?.length) {
|
|
52054
|
+
const typeFilter = args.fileTypes.map((t) => `filetype:${t.replace(/^\./, "")}`).join(" OR ");
|
|
52055
|
+
queryString = `(${queryString}) (${typeFilter})`;
|
|
52056
|
+
}
|
|
52057
|
+
const searchRequest = {
|
|
52058
|
+
requests: [
|
|
52059
|
+
{
|
|
52060
|
+
entityTypes: ["driveItem"],
|
|
52061
|
+
query: { queryString },
|
|
52062
|
+
from: 0,
|
|
52063
|
+
size: limit,
|
|
52064
|
+
fields: [
|
|
52065
|
+
"id",
|
|
52066
|
+
"name",
|
|
52067
|
+
"size",
|
|
52068
|
+
"webUrl",
|
|
52069
|
+
"lastModifiedDateTime",
|
|
52070
|
+
"parentReference",
|
|
52071
|
+
"file"
|
|
52072
|
+
]
|
|
52073
|
+
}
|
|
52074
|
+
]
|
|
52075
|
+
};
|
|
52076
|
+
const data = await microsoftFetch(connector, "/search/query", {
|
|
52077
|
+
method: "POST",
|
|
52078
|
+
body: searchRequest,
|
|
52079
|
+
userId: effectiveUserId,
|
|
52080
|
+
accountId: effectiveAccountId
|
|
52081
|
+
});
|
|
52082
|
+
const container = data.value?.[0]?.hitsContainers?.[0];
|
|
52083
|
+
if (!container?.hits?.length) {
|
|
52084
|
+
return {
|
|
52085
|
+
success: true,
|
|
52086
|
+
results: [],
|
|
52087
|
+
totalCount: 0,
|
|
52088
|
+
hasMore: false
|
|
52089
|
+
};
|
|
52090
|
+
}
|
|
52091
|
+
const results = container.hits.map((hit) => {
|
|
52092
|
+
const resource = hit.resource;
|
|
52093
|
+
return {
|
|
52094
|
+
name: resource.name,
|
|
52095
|
+
path: resource.parentReference?.path?.replace(/\/drive\/root:/, "") || void 0,
|
|
52096
|
+
site: resource.parentReference?.siteId || void 0,
|
|
52097
|
+
snippet: hit.summary || void 0,
|
|
52098
|
+
size: resource.size,
|
|
52099
|
+
sizeFormatted: formatFileSize(resource.size),
|
|
52100
|
+
webUrl: resource.webUrl,
|
|
52101
|
+
id: resource.id,
|
|
52102
|
+
lastModified: resource.lastModifiedDateTime
|
|
52103
|
+
};
|
|
52104
|
+
});
|
|
52105
|
+
return {
|
|
52106
|
+
success: true,
|
|
52107
|
+
results,
|
|
52108
|
+
totalCount: container.total,
|
|
52109
|
+
hasMore: container.moreResultsAvailable
|
|
52110
|
+
};
|
|
52111
|
+
} catch (error) {
|
|
52112
|
+
return {
|
|
52113
|
+
success: false,
|
|
52114
|
+
error: `Search failed: ${error instanceof Error ? error.message : String(error)}`
|
|
52115
|
+
};
|
|
52116
|
+
}
|
|
52117
|
+
}
|
|
52118
|
+
};
|
|
52119
|
+
}
|
|
52120
|
+
async function searchViaDriveEndpoint(connector, args, limit, effectiveUserId, effectiveAccountId) {
|
|
52121
|
+
const drivePrefix = `/sites/${args.siteId}/drive`;
|
|
52122
|
+
const escapedQuery = args.query.replace(/'/g, "''");
|
|
52123
|
+
const endpoint = `${drivePrefix}/root/search(q='${escapedQuery}')`;
|
|
52124
|
+
const data = await microsoftFetch(connector, endpoint, {
|
|
52125
|
+
userId: effectiveUserId,
|
|
52126
|
+
accountId: effectiveAccountId,
|
|
52127
|
+
queryParams: {
|
|
52128
|
+
"$top": limit,
|
|
52129
|
+
"$select": "id,name,size,webUrl,lastModifiedDateTime,parentReference,file"
|
|
52130
|
+
}
|
|
52131
|
+
});
|
|
52132
|
+
let items = data.value || [];
|
|
52133
|
+
if (args.fileTypes?.length) {
|
|
52134
|
+
const extSet = new Set(args.fileTypes.map((t) => t.replace(/^\./, "").toLowerCase()));
|
|
52135
|
+
items = items.filter((item) => {
|
|
52136
|
+
const ext = item.name.split(".").pop()?.toLowerCase();
|
|
52137
|
+
return ext && extSet.has(ext);
|
|
52138
|
+
});
|
|
52139
|
+
}
|
|
52140
|
+
const results = items.map((item) => ({
|
|
52141
|
+
name: item.name,
|
|
52142
|
+
path: item.parentReference?.path?.replace(/\/drive\/root:/, "") || void 0,
|
|
52143
|
+
site: item.parentReference?.siteId || void 0,
|
|
52144
|
+
snippet: void 0,
|
|
52145
|
+
size: item.size,
|
|
52146
|
+
sizeFormatted: formatFileSize(item.size),
|
|
52147
|
+
webUrl: item.webUrl,
|
|
52148
|
+
id: item.id,
|
|
52149
|
+
lastModified: item.lastModifiedDateTime
|
|
52150
|
+
}));
|
|
52151
|
+
return {
|
|
52152
|
+
success: true,
|
|
52153
|
+
results,
|
|
52154
|
+
totalCount: results.length,
|
|
52155
|
+
hasMore: !!data["@odata.nextLink"]
|
|
52156
|
+
};
|
|
52157
|
+
}
|
|
52158
|
+
|
|
49773
52159
|
// src/tools/microsoft/register.ts
|
|
49774
52160
|
function registerMicrosoftTools() {
|
|
49775
52161
|
ConnectorTools.registerService("microsoft", (connector, userId) => {
|
|
49776
52162
|
return [
|
|
52163
|
+
// Email
|
|
49777
52164
|
createDraftEmailTool(connector, userId),
|
|
49778
52165
|
createSendEmailTool(connector, userId),
|
|
52166
|
+
// Meetings
|
|
49779
52167
|
createMeetingTool(connector, userId),
|
|
49780
52168
|
createEditMeetingTool(connector, userId),
|
|
49781
52169
|
createGetMeetingTranscriptTool(connector, userId),
|
|
49782
|
-
createFindMeetingSlotsTool(connector, userId)
|
|
52170
|
+
createFindMeetingSlotsTool(connector, userId),
|
|
52171
|
+
// Files (OneDrive / SharePoint)
|
|
52172
|
+
createMicrosoftReadFileTool(connector, userId),
|
|
52173
|
+
createMicrosoftListFilesTool(connector, userId),
|
|
52174
|
+
createMicrosoftSearchFilesTool(connector, userId)
|
|
49783
52175
|
];
|
|
49784
52176
|
});
|
|
49785
52177
|
}
|
|
@@ -51133,6 +53525,311 @@ function createCustomToolTest() {
|
|
|
51133
53525
|
}
|
|
51134
53526
|
var customToolTest = createCustomToolTest();
|
|
51135
53527
|
|
|
53528
|
+
// src/tools/routines/resolveStorage.ts
|
|
53529
|
+
init_StorageRegistry();
|
|
53530
|
+
function buildStorageContext3(toolContext) {
|
|
53531
|
+
const global2 = exports.StorageRegistry.getContext();
|
|
53532
|
+
if (global2) return global2;
|
|
53533
|
+
if (toolContext?.userId) return { userId: toolContext.userId };
|
|
53534
|
+
return void 0;
|
|
53535
|
+
}
|
|
53536
|
+
function resolveRoutineDefinitionStorage(explicit, toolContext) {
|
|
53537
|
+
if (explicit) return explicit;
|
|
53538
|
+
const factory = exports.StorageRegistry.get("routineDefinitions");
|
|
53539
|
+
if (factory) {
|
|
53540
|
+
return factory(buildStorageContext3(toolContext));
|
|
53541
|
+
}
|
|
53542
|
+
return new FileRoutineDefinitionStorage();
|
|
53543
|
+
}
|
|
53544
|
+
|
|
53545
|
+
// src/tools/routines/generateRoutine.ts
|
|
53546
|
+
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.
|
|
53547
|
+
|
|
53548
|
+
## Routine Structure
|
|
53549
|
+
|
|
53550
|
+
A routine definition has these fields:
|
|
53551
|
+
- **name** (required): Human-readable name (e.g., "Research and Summarize Topic")
|
|
53552
|
+
- **description** (required): What this routine accomplishes
|
|
53553
|
+
- **version**: Semver string for tracking evolution (e.g., "1.0.0")
|
|
53554
|
+
- **author**: Creator name or identifier
|
|
53555
|
+
- **tags**: Array of strings for categorization and filtering (e.g., ["research", "analysis"])
|
|
53556
|
+
- **instructions**: Additional text injected into the agent's system prompt when executing this routine. Use for behavioral guidance, tone, constraints.
|
|
53557
|
+
- **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.
|
|
53558
|
+
- **requiredTools**: Tool names that must be available before starting (e.g., ["web_fetch", "memory_store"])
|
|
53559
|
+
- **requiredPlugins**: Plugin names that must be enabled (e.g., ["working_memory"])
|
|
53560
|
+
- **concurrency**: { maxParallelTasks: number, strategy: "fifo"|"priority"|"shortest-first", failureMode?: "fail-fast"|"continue"|"fail-all" }
|
|
53561
|
+
- **allowDynamicTasks**: If true, the LLM can add/modify tasks during execution (default: false)
|
|
53562
|
+
- **tasks** (required): Array of TaskInput objects defining the workflow steps
|
|
53563
|
+
|
|
53564
|
+
## Task Structure
|
|
53565
|
+
|
|
53566
|
+
Each task in the tasks array has:
|
|
53567
|
+
- **name** (required): Unique name within the routine (used for dependency references)
|
|
53568
|
+
- **description** (required): What this task should accomplish \u2014 the agent uses this as its goal
|
|
53569
|
+
- **dependsOn**: Array of task names that must complete before this task starts (e.g., ["Gather Data", "Validate Input"])
|
|
53570
|
+
- **suggestedTools**: Tool names the agent should prefer for this task (advisory, not enforced)
|
|
53571
|
+
- **expectedOutput**: Description of what the task should produce \u2014 helps the agent know when it's done
|
|
53572
|
+
- **maxAttempts**: Max retry count on failure (default: 3)
|
|
53573
|
+
- **condition**: Execute only if a condition is met (see Conditions below)
|
|
53574
|
+
- **controlFlow**: Map, fold, or until loop (see Control Flow below)
|
|
53575
|
+
- **validation**: Completion validation settings (see Validation below)
|
|
53576
|
+
- **execution**: { parallel?: boolean, maxConcurrency?: number, priority?: number, maxIterations?: number }
|
|
53577
|
+
- **metadata**: Arbitrary key-value pairs for extensions
|
|
53578
|
+
|
|
53579
|
+
## Control Flow Types
|
|
53580
|
+
|
|
53581
|
+
Tasks can have a controlFlow field for iteration patterns:
|
|
53582
|
+
|
|
53583
|
+
### map \u2014 Iterate over an array, run sub-tasks per element
|
|
53584
|
+
\`\`\`json
|
|
53585
|
+
{
|
|
53586
|
+
"type": "map",
|
|
53587
|
+
"source": { "task": "Fetch Items" },
|
|
53588
|
+
"tasks": [
|
|
53589
|
+
{ "name": "Process Item", "description": "Process {{map.item}} ({{map.index}}/{{map.total}})" }
|
|
53590
|
+
],
|
|
53591
|
+
"resultKey": "processed_items",
|
|
53592
|
+
"maxIterations": 100,
|
|
53593
|
+
"iterationTimeoutMs": 30000
|
|
53594
|
+
}
|
|
53595
|
+
\`\`\`
|
|
53596
|
+
|
|
53597
|
+
### fold \u2014 Accumulate a result across array elements
|
|
53598
|
+
\`\`\`json
|
|
53599
|
+
{
|
|
53600
|
+
"type": "fold",
|
|
53601
|
+
"source": { "key": "data_points", "path": "results" },
|
|
53602
|
+
"tasks": [
|
|
53603
|
+
{ "name": "Merge Entry", "description": "Merge {{map.item}} into {{fold.accumulator}}" }
|
|
53604
|
+
],
|
|
53605
|
+
"initialValue": "",
|
|
53606
|
+
"resultKey": "merged_result",
|
|
53607
|
+
"maxIterations": 50
|
|
53608
|
+
}
|
|
53609
|
+
\`\`\`
|
|
53610
|
+
|
|
53611
|
+
### until \u2014 Loop until a condition is met
|
|
53612
|
+
\`\`\`json
|
|
53613
|
+
{
|
|
53614
|
+
"type": "until",
|
|
53615
|
+
"tasks": [
|
|
53616
|
+
{ "name": "Refine Draft", "description": "Improve the current draft based on feedback" }
|
|
53617
|
+
],
|
|
53618
|
+
"condition": { "memoryKey": "quality_score", "operator": "greater_than", "value": 80, "onFalse": "skip" },
|
|
53619
|
+
"maxIterations": 5,
|
|
53620
|
+
"iterationKey": "refinement_round"
|
|
53621
|
+
}
|
|
53622
|
+
\`\`\`
|
|
53623
|
+
|
|
53624
|
+
### Source field
|
|
53625
|
+
The \`source\` field in map/fold can be:
|
|
53626
|
+
- A string: direct memory key lookup (e.g., "my_items")
|
|
53627
|
+
- \`{ task: "TaskName" }\`: resolves the output of a completed dependency task
|
|
53628
|
+
- \`{ key: "memoryKey" }\`: direct memory key lookup
|
|
53629
|
+
- \`{ key: "memoryKey", path: "data.items" }\`: memory key with JSON path extraction
|
|
53630
|
+
|
|
53631
|
+
### Sub-routine tasks
|
|
53632
|
+
Tasks inside controlFlow.tasks have the same shape as regular tasks (name, description, dependsOn, suggestedTools, etc.) but execute within the control flow context.
|
|
53633
|
+
|
|
53634
|
+
## Template Placeholders
|
|
53635
|
+
|
|
53636
|
+
Use these in task descriptions and expectedOutput:
|
|
53637
|
+
- \`{{param.NAME}}\` \u2014 routine parameter value
|
|
53638
|
+
- \`{{map.item}}\` \u2014 current element in map/fold iteration
|
|
53639
|
+
- \`{{map.index}}\` \u2014 current 0-based iteration index
|
|
53640
|
+
- \`{{map.total}}\` \u2014 total number of elements
|
|
53641
|
+
- \`{{fold.accumulator}}\` \u2014 current accumulated value in fold
|
|
53642
|
+
|
|
53643
|
+
## Task Conditions
|
|
53644
|
+
|
|
53645
|
+
Execute a task conditionally based on memory state:
|
|
53646
|
+
\`\`\`json
|
|
53647
|
+
{
|
|
53648
|
+
"memoryKey": "user_preference",
|
|
53649
|
+
"operator": "equals",
|
|
53650
|
+
"value": "detailed",
|
|
53651
|
+
"onFalse": "skip"
|
|
53652
|
+
}
|
|
53653
|
+
\`\`\`
|
|
53654
|
+
|
|
53655
|
+
Operators: "exists", "not_exists", "equals", "contains", "truthy", "greater_than", "less_than"
|
|
53656
|
+
onFalse actions: "skip" (mark skipped), "fail" (mark failed), "wait" (block until condition met)
|
|
53657
|
+
|
|
53658
|
+
## Validation
|
|
53659
|
+
|
|
53660
|
+
Enable completion validation to verify task quality:
|
|
53661
|
+
\`\`\`json
|
|
53662
|
+
{
|
|
53663
|
+
"skipReflection": false,
|
|
53664
|
+
"completionCriteria": [
|
|
53665
|
+
"Response contains at least 3 specific examples",
|
|
53666
|
+
"All requested sections are present"
|
|
53667
|
+
],
|
|
53668
|
+
"minCompletionScore": 80,
|
|
53669
|
+
"requiredMemoryKeys": ["result_data"],
|
|
53670
|
+
"mode": "strict"
|
|
53671
|
+
}
|
|
53672
|
+
\`\`\`
|
|
53673
|
+
|
|
53674
|
+
By default, skipReflection is true (validation auto-passes). Set to false and provide completionCriteria to enable LLM self-reflection validation.
|
|
53675
|
+
|
|
53676
|
+
## Best Practices
|
|
53677
|
+
|
|
53678
|
+
1. **Task naming**: Use clear, action-oriented names (e.g., "Research Topic", "Generate Summary"). Names are used in dependsOn references.
|
|
53679
|
+
2. **Dependency chaining**: Build pipelines by having each task depend on the previous one. Independent tasks can run in parallel.
|
|
53680
|
+
3. **Control flow vs sequential**: Use map/fold when iterating over dynamic data. Use sequential tasks for fixed multi-step workflows.
|
|
53681
|
+
4. **Parameters**: Define parameters for any value that should vary between executions. Always provide a description.
|
|
53682
|
+
5. **Instructions**: Use the instructions field for behavioral guidance that applies to all tasks in the routine.
|
|
53683
|
+
6. **Keep tasks focused**: Each task should have a single clear goal. Break complex work into multiple dependent tasks.
|
|
53684
|
+
7. **Expected output**: Always specify expectedOutput \u2014 it helps the agent know when it's done and what format to produce.`;
|
|
53685
|
+
function createGenerateRoutine(storage) {
|
|
53686
|
+
return {
|
|
53687
|
+
definition: {
|
|
53688
|
+
type: "function",
|
|
53689
|
+
function: {
|
|
53690
|
+
name: "generate_routine",
|
|
53691
|
+
description: TOOL_DESCRIPTION,
|
|
53692
|
+
parameters: {
|
|
53693
|
+
type: "object",
|
|
53694
|
+
properties: {
|
|
53695
|
+
definition: {
|
|
53696
|
+
type: "object",
|
|
53697
|
+
description: "Complete routine definition input",
|
|
53698
|
+
properties: {
|
|
53699
|
+
name: { type: "string", description: "Human-readable routine name" },
|
|
53700
|
+
description: { type: "string", description: "What this routine accomplishes" },
|
|
53701
|
+
version: { type: "string", description: 'Semver version string (e.g., "1.0.0")' },
|
|
53702
|
+
author: { type: "string", description: "Creator name or identifier" },
|
|
53703
|
+
tags: { type: "array", items: { type: "string" }, description: "Tags for categorization" },
|
|
53704
|
+
instructions: { type: "string", description: "Additional instructions injected into system prompt during execution" },
|
|
53705
|
+
parameters: {
|
|
53706
|
+
type: "array",
|
|
53707
|
+
description: "Input parameters for reusable routines",
|
|
53708
|
+
items: {
|
|
53709
|
+
type: "object",
|
|
53710
|
+
properties: {
|
|
53711
|
+
name: { type: "string", description: "Parameter name (referenced as {{param.name}})" },
|
|
53712
|
+
description: { type: "string", description: "Human-readable description" },
|
|
53713
|
+
required: { type: "boolean", description: "Whether this parameter must be provided (default: false)" },
|
|
53714
|
+
default: { description: "Default value when not provided" }
|
|
53715
|
+
},
|
|
53716
|
+
required: ["name", "description"]
|
|
53717
|
+
}
|
|
53718
|
+
},
|
|
53719
|
+
requiredTools: { type: "array", items: { type: "string" }, description: "Tool names that must be available" },
|
|
53720
|
+
requiredPlugins: { type: "array", items: { type: "string" }, description: "Plugin names that must be enabled" },
|
|
53721
|
+
concurrency: {
|
|
53722
|
+
type: "object",
|
|
53723
|
+
description: "Concurrency settings for parallel task execution",
|
|
53724
|
+
properties: {
|
|
53725
|
+
maxParallelTasks: { type: "number", description: "Maximum tasks running in parallel" },
|
|
53726
|
+
strategy: { type: "string", enum: ["fifo", "priority", "shortest-first"], description: "Task selection strategy" },
|
|
53727
|
+
failureMode: { type: "string", enum: ["fail-fast", "continue", "fail-all"], description: "How to handle failures in parallel" }
|
|
53728
|
+
},
|
|
53729
|
+
required: ["maxParallelTasks", "strategy"]
|
|
53730
|
+
},
|
|
53731
|
+
allowDynamicTasks: { type: "boolean", description: "Allow LLM to add/modify tasks during execution (default: false)" },
|
|
53732
|
+
tasks: {
|
|
53733
|
+
type: "array",
|
|
53734
|
+
description: "Array of task definitions forming the workflow",
|
|
53735
|
+
items: {
|
|
53736
|
+
type: "object",
|
|
53737
|
+
properties: {
|
|
53738
|
+
name: { type: "string", description: "Unique task name (used in dependsOn references)" },
|
|
53739
|
+
description: { type: "string", description: "What this task should accomplish" },
|
|
53740
|
+
dependsOn: { type: "array", items: { type: "string" }, description: "Task names that must complete first" },
|
|
53741
|
+
suggestedTools: { type: "array", items: { type: "string" }, description: "Preferred tool names for this task" },
|
|
53742
|
+
expectedOutput: { type: "string", description: "Description of expected task output" },
|
|
53743
|
+
maxAttempts: { type: "number", description: "Max retries on failure (default: 3)" },
|
|
53744
|
+
condition: {
|
|
53745
|
+
type: "object",
|
|
53746
|
+
description: "Conditional execution based on memory state",
|
|
53747
|
+
properties: {
|
|
53748
|
+
memoryKey: { type: "string", description: "Memory key to check" },
|
|
53749
|
+
operator: { type: "string", enum: ["exists", "not_exists", "equals", "contains", "truthy", "greater_than", "less_than"] },
|
|
53750
|
+
value: { description: "Value to compare against" },
|
|
53751
|
+
onFalse: { type: "string", enum: ["skip", "fail", "wait"], description: "Action when condition is false" }
|
|
53752
|
+
},
|
|
53753
|
+
required: ["memoryKey", "operator", "onFalse"]
|
|
53754
|
+
},
|
|
53755
|
+
controlFlow: {
|
|
53756
|
+
type: "object",
|
|
53757
|
+
description: "Control flow for iteration (map, fold, or until)",
|
|
53758
|
+
properties: {
|
|
53759
|
+
type: { type: "string", enum: ["map", "fold", "until"], description: "Control flow type" },
|
|
53760
|
+
source: { description: 'Source data: string key, { task: "name" }, { key: "key" }, or { key: "key", path: "json.path" }' },
|
|
53761
|
+
tasks: { type: "array", description: "Sub-routine tasks to execute per iteration", items: { type: "object" } },
|
|
53762
|
+
resultKey: { type: "string", description: "Memory key for collected results" },
|
|
53763
|
+
initialValue: { description: "Starting accumulator value (fold only)" },
|
|
53764
|
+
condition: { type: "object", description: "Exit condition (until only)" },
|
|
53765
|
+
maxIterations: { type: "number", description: "Cap on iterations" },
|
|
53766
|
+
iterationKey: { type: "string", description: "Memory key for iteration index (until only)" },
|
|
53767
|
+
iterationTimeoutMs: { type: "number", description: "Timeout per iteration in ms" }
|
|
53768
|
+
},
|
|
53769
|
+
required: ["type", "tasks"]
|
|
53770
|
+
},
|
|
53771
|
+
validation: {
|
|
53772
|
+
type: "object",
|
|
53773
|
+
description: "Completion validation settings",
|
|
53774
|
+
properties: {
|
|
53775
|
+
skipReflection: { type: "boolean", description: "Set to false to enable LLM self-reflection validation" },
|
|
53776
|
+
completionCriteria: { type: "array", items: { type: "string" }, description: "Natural language criteria for completion" },
|
|
53777
|
+
minCompletionScore: { type: "number", description: "Minimum score (0-100) to pass (default: 80)" },
|
|
53778
|
+
requiredMemoryKeys: { type: "array", items: { type: "string" }, description: "Memory keys that must exist after completion" },
|
|
53779
|
+
mode: { type: "string", enum: ["strict", "warn"], description: "Validation failure mode (default: strict)" },
|
|
53780
|
+
requireUserApproval: { type: "string", enum: ["never", "uncertain", "always"], description: "When to ask user for approval" },
|
|
53781
|
+
customValidator: { type: "string", description: "Custom validation hook name" }
|
|
53782
|
+
}
|
|
53783
|
+
},
|
|
53784
|
+
execution: {
|
|
53785
|
+
type: "object",
|
|
53786
|
+
description: "Execution settings",
|
|
53787
|
+
properties: {
|
|
53788
|
+
parallel: { type: "boolean", description: "Can run in parallel with other parallel tasks" },
|
|
53789
|
+
maxConcurrency: { type: "number", description: "Max concurrent sub-work" },
|
|
53790
|
+
priority: { type: "number", description: "Higher = executed first" },
|
|
53791
|
+
maxIterations: { type: "number", description: "Max LLM iterations per task (default: 50)" }
|
|
53792
|
+
}
|
|
53793
|
+
},
|
|
53794
|
+
metadata: { type: "object", description: "Arbitrary key-value metadata" }
|
|
53795
|
+
},
|
|
53796
|
+
required: ["name", "description"]
|
|
53797
|
+
}
|
|
53798
|
+
},
|
|
53799
|
+
metadata: { type: "object", description: "Arbitrary routine-level metadata" }
|
|
53800
|
+
},
|
|
53801
|
+
required: ["name", "description", "tasks"]
|
|
53802
|
+
}
|
|
53803
|
+
},
|
|
53804
|
+
required: ["definition"]
|
|
53805
|
+
}
|
|
53806
|
+
}
|
|
53807
|
+
},
|
|
53808
|
+
permission: { scope: "session", riskLevel: "medium" },
|
|
53809
|
+
execute: async (args, context) => {
|
|
53810
|
+
try {
|
|
53811
|
+
const userId = context?.userId;
|
|
53812
|
+
const s = resolveRoutineDefinitionStorage(storage, context);
|
|
53813
|
+
const routineDefinition = createRoutineDefinition(args.definition);
|
|
53814
|
+
await s.save(userId, routineDefinition);
|
|
53815
|
+
return {
|
|
53816
|
+
success: true,
|
|
53817
|
+
id: routineDefinition.id,
|
|
53818
|
+
name: routineDefinition.name,
|
|
53819
|
+
storagePath: s.getPath(userId)
|
|
53820
|
+
};
|
|
53821
|
+
} catch (error) {
|
|
53822
|
+
return {
|
|
53823
|
+
success: false,
|
|
53824
|
+
error: error.message
|
|
53825
|
+
};
|
|
53826
|
+
}
|
|
53827
|
+
},
|
|
53828
|
+
describeCall: (args) => args.definition?.name ?? "routine"
|
|
53829
|
+
};
|
|
53830
|
+
}
|
|
53831
|
+
var generateRoutine = createGenerateRoutine();
|
|
53832
|
+
|
|
51136
53833
|
// src/tools/registry.generated.ts
|
|
51137
53834
|
var toolRegistry = [
|
|
51138
53835
|
{
|
|
@@ -51360,6 +54057,15 @@ var toolRegistry = [
|
|
|
51360
54057
|
tool: jsonManipulator,
|
|
51361
54058
|
safeByDefault: true
|
|
51362
54059
|
},
|
|
54060
|
+
{
|
|
54061
|
+
name: "generate_routine",
|
|
54062
|
+
exportName: "generateRoutine",
|
|
54063
|
+
displayName: "Generate Routine",
|
|
54064
|
+
category: "routines",
|
|
54065
|
+
description: "Complete routine definition input",
|
|
54066
|
+
tool: generateRoutine,
|
|
54067
|
+
safeByDefault: false
|
|
54068
|
+
},
|
|
51363
54069
|
{
|
|
51364
54070
|
name: "bash",
|
|
51365
54071
|
exportName: "bash",
|
|
@@ -51789,6 +54495,127 @@ REMEMBER: Keep it conversational, ask one question at a time, and only output th
|
|
|
51789
54495
|
}
|
|
51790
54496
|
};
|
|
51791
54497
|
|
|
54498
|
+
// src/infrastructure/scheduling/SimpleScheduler.ts
|
|
54499
|
+
var SimpleScheduler = class {
|
|
54500
|
+
timers = /* @__PURE__ */ new Map();
|
|
54501
|
+
_isDestroyed = false;
|
|
54502
|
+
schedule(id, spec, callback) {
|
|
54503
|
+
if (this._isDestroyed) throw new Error("Scheduler has been destroyed");
|
|
54504
|
+
if (spec.cron) {
|
|
54505
|
+
throw new Error(
|
|
54506
|
+
`SimpleScheduler does not support cron expressions. Use a cron-capable scheduler implementation (e.g. node-cron, croner) or convert to intervalMs.`
|
|
54507
|
+
);
|
|
54508
|
+
}
|
|
54509
|
+
if (this.timers.has(id)) {
|
|
54510
|
+
this.cancel(id);
|
|
54511
|
+
}
|
|
54512
|
+
if (spec.intervalMs != null) {
|
|
54513
|
+
const timer = setInterval(() => {
|
|
54514
|
+
try {
|
|
54515
|
+
const result = callback();
|
|
54516
|
+
if (result && typeof result.catch === "function") {
|
|
54517
|
+
result.catch(() => {
|
|
54518
|
+
});
|
|
54519
|
+
}
|
|
54520
|
+
} catch {
|
|
54521
|
+
}
|
|
54522
|
+
}, spec.intervalMs);
|
|
54523
|
+
this.timers.set(id, { timer, type: "interval" });
|
|
54524
|
+
} else if (spec.once != null) {
|
|
54525
|
+
const delay = Math.max(0, spec.once - Date.now());
|
|
54526
|
+
const timer = setTimeout(() => {
|
|
54527
|
+
this.timers.delete(id);
|
|
54528
|
+
try {
|
|
54529
|
+
const result = callback();
|
|
54530
|
+
if (result && typeof result.catch === "function") {
|
|
54531
|
+
result.catch(() => {
|
|
54532
|
+
});
|
|
54533
|
+
}
|
|
54534
|
+
} catch {
|
|
54535
|
+
}
|
|
54536
|
+
}, delay);
|
|
54537
|
+
this.timers.set(id, { timer, type: "timeout" });
|
|
54538
|
+
} else {
|
|
54539
|
+
throw new Error("ScheduleSpec must have at least one of: cron, intervalMs, once");
|
|
54540
|
+
}
|
|
54541
|
+
return {
|
|
54542
|
+
id,
|
|
54543
|
+
cancel: () => this.cancel(id)
|
|
54544
|
+
};
|
|
54545
|
+
}
|
|
54546
|
+
cancel(id) {
|
|
54547
|
+
const entry = this.timers.get(id);
|
|
54548
|
+
if (!entry) return;
|
|
54549
|
+
if (entry.type === "interval") {
|
|
54550
|
+
clearInterval(entry.timer);
|
|
54551
|
+
} else {
|
|
54552
|
+
clearTimeout(entry.timer);
|
|
54553
|
+
}
|
|
54554
|
+
this.timers.delete(id);
|
|
54555
|
+
}
|
|
54556
|
+
cancelAll() {
|
|
54557
|
+
for (const [id] of this.timers) {
|
|
54558
|
+
this.cancel(id);
|
|
54559
|
+
}
|
|
54560
|
+
}
|
|
54561
|
+
has(id) {
|
|
54562
|
+
return this.timers.has(id);
|
|
54563
|
+
}
|
|
54564
|
+
destroy() {
|
|
54565
|
+
if (this._isDestroyed) return;
|
|
54566
|
+
this.cancelAll();
|
|
54567
|
+
this._isDestroyed = true;
|
|
54568
|
+
}
|
|
54569
|
+
get isDestroyed() {
|
|
54570
|
+
return this._isDestroyed;
|
|
54571
|
+
}
|
|
54572
|
+
};
|
|
54573
|
+
|
|
54574
|
+
// src/infrastructure/triggers/EventEmitterTrigger.ts
|
|
54575
|
+
var EventEmitterTrigger = class {
|
|
54576
|
+
listeners = /* @__PURE__ */ new Map();
|
|
54577
|
+
_isDestroyed = false;
|
|
54578
|
+
/**
|
|
54579
|
+
* Register a listener for an event. Returns an unsubscribe function.
|
|
54580
|
+
*/
|
|
54581
|
+
on(event, callback) {
|
|
54582
|
+
if (this._isDestroyed) throw new Error("EventEmitterTrigger has been destroyed");
|
|
54583
|
+
if (!this.listeners.has(event)) {
|
|
54584
|
+
this.listeners.set(event, /* @__PURE__ */ new Set());
|
|
54585
|
+
}
|
|
54586
|
+
this.listeners.get(event).add(callback);
|
|
54587
|
+
return () => {
|
|
54588
|
+
this.listeners.get(event)?.delete(callback);
|
|
54589
|
+
};
|
|
54590
|
+
}
|
|
54591
|
+
/**
|
|
54592
|
+
* Emit an event to all registered listeners.
|
|
54593
|
+
*/
|
|
54594
|
+
emit(event, payload) {
|
|
54595
|
+
if (this._isDestroyed) return;
|
|
54596
|
+
const callbacks = this.listeners.get(event);
|
|
54597
|
+
if (!callbacks) return;
|
|
54598
|
+
for (const cb of callbacks) {
|
|
54599
|
+
try {
|
|
54600
|
+
const result = cb(payload);
|
|
54601
|
+
if (result && typeof result.catch === "function") {
|
|
54602
|
+
result.catch(() => {
|
|
54603
|
+
});
|
|
54604
|
+
}
|
|
54605
|
+
} catch {
|
|
54606
|
+
}
|
|
54607
|
+
}
|
|
54608
|
+
}
|
|
54609
|
+
destroy() {
|
|
54610
|
+
if (this._isDestroyed) return;
|
|
54611
|
+
this.listeners.clear();
|
|
54612
|
+
this._isDestroyed = true;
|
|
54613
|
+
}
|
|
54614
|
+
get isDestroyed() {
|
|
54615
|
+
return this._isDestroyed;
|
|
54616
|
+
}
|
|
54617
|
+
};
|
|
54618
|
+
|
|
51792
54619
|
exports.AGENT_DEFINITION_FORMAT_VERSION = AGENT_DEFINITION_FORMAT_VERSION;
|
|
51793
54620
|
exports.AIError = AIError;
|
|
51794
54621
|
exports.APPROVAL_STATE_VERSION = APPROVAL_STATE_VERSION;
|
|
@@ -51825,6 +54652,7 @@ exports.DefaultCompactionStrategy = DefaultCompactionStrategy;
|
|
|
51825
54652
|
exports.DependencyCycleError = DependencyCycleError;
|
|
51826
54653
|
exports.DocumentReader = DocumentReader;
|
|
51827
54654
|
exports.ErrorHandler = ErrorHandler;
|
|
54655
|
+
exports.EventEmitterTrigger = EventEmitterTrigger;
|
|
51828
54656
|
exports.ExecutionContext = ExecutionContext;
|
|
51829
54657
|
exports.ExternalDependencyHandler = ExternalDependencyHandler;
|
|
51830
54658
|
exports.FileAgentDefinitionStorage = FileAgentDefinitionStorage;
|
|
@@ -51890,6 +54718,7 @@ exports.ScrapeProvider = ScrapeProvider;
|
|
|
51890
54718
|
exports.SearchProvider = SearchProvider;
|
|
51891
54719
|
exports.SerperProvider = SerperProvider;
|
|
51892
54720
|
exports.Services = Services;
|
|
54721
|
+
exports.SimpleScheduler = SimpleScheduler;
|
|
51893
54722
|
exports.SpeechToText = SpeechToText;
|
|
51894
54723
|
exports.StrategyRegistry = StrategyRegistry;
|
|
51895
54724
|
exports.StreamEventType = StreamEventType;
|
|
@@ -51905,6 +54734,8 @@ exports.TavilyProvider = TavilyProvider;
|
|
|
51905
54734
|
exports.TextToSpeech = TextToSpeech;
|
|
51906
54735
|
exports.TokenBucketRateLimiter = TokenBucketRateLimiter;
|
|
51907
54736
|
exports.ToolCallState = ToolCallState;
|
|
54737
|
+
exports.ToolCatalogPluginNextGen = ToolCatalogPluginNextGen;
|
|
54738
|
+
exports.ToolCatalogRegistry = ToolCatalogRegistry;
|
|
51908
54739
|
exports.ToolExecutionError = ToolExecutionError;
|
|
51909
54740
|
exports.ToolExecutionPipeline = ToolExecutionPipeline;
|
|
51910
54741
|
exports.ToolManager = ToolManager;
|
|
@@ -51968,6 +54799,7 @@ exports.createEditFileTool = createEditFileTool;
|
|
|
51968
54799
|
exports.createEditMeetingTool = createEditMeetingTool;
|
|
51969
54800
|
exports.createEstimator = createEstimator;
|
|
51970
54801
|
exports.createExecuteJavaScriptTool = createExecuteJavaScriptTool;
|
|
54802
|
+
exports.createExecutionRecorder = createExecutionRecorder;
|
|
51971
54803
|
exports.createFileAgentDefinitionStorage = createFileAgentDefinitionStorage;
|
|
51972
54804
|
exports.createFileContextStorage = createFileContextStorage;
|
|
51973
54805
|
exports.createFileCustomToolStorage = createFileCustomToolStorage;
|
|
@@ -51985,6 +54817,9 @@ exports.createListDirectoryTool = createListDirectoryTool;
|
|
|
51985
54817
|
exports.createMeetingTool = createMeetingTool;
|
|
51986
54818
|
exports.createMessageWithImages = createMessageWithImages;
|
|
51987
54819
|
exports.createMetricsCollector = createMetricsCollector;
|
|
54820
|
+
exports.createMicrosoftListFilesTool = createMicrosoftListFilesTool;
|
|
54821
|
+
exports.createMicrosoftReadFileTool = createMicrosoftReadFileTool;
|
|
54822
|
+
exports.createMicrosoftSearchFilesTool = createMicrosoftSearchFilesTool;
|
|
51988
54823
|
exports.createPRCommentsTool = createPRCommentsTool;
|
|
51989
54824
|
exports.createPRFilesTool = createPRFilesTool;
|
|
51990
54825
|
exports.createPlan = createPlan;
|
|
@@ -51992,11 +54827,13 @@ exports.createProvider = createProvider;
|
|
|
51992
54827
|
exports.createReadFileTool = createReadFileTool;
|
|
51993
54828
|
exports.createRoutineDefinition = createRoutineDefinition;
|
|
51994
54829
|
exports.createRoutineExecution = createRoutineExecution;
|
|
54830
|
+
exports.createRoutineExecutionRecord = createRoutineExecutionRecord;
|
|
51995
54831
|
exports.createSearchCodeTool = createSearchCodeTool;
|
|
51996
54832
|
exports.createSearchFilesTool = createSearchFilesTool;
|
|
51997
54833
|
exports.createSendEmailTool = createSendEmailTool;
|
|
51998
54834
|
exports.createSpeechToTextTool = createSpeechToTextTool;
|
|
51999
54835
|
exports.createTask = createTask;
|
|
54836
|
+
exports.createTaskSnapshots = createTaskSnapshots;
|
|
52000
54837
|
exports.createTextMessage = createTextMessage;
|
|
52001
54838
|
exports.createTextToSpeechTool = createTextToSpeechTool;
|
|
52002
54839
|
exports.createVideoProvider = createVideoProvider;
|
|
@@ -52026,6 +54863,7 @@ exports.detectServiceFromURL = detectServiceFromURL;
|
|
|
52026
54863
|
exports.developerTools = developerTools;
|
|
52027
54864
|
exports.documentToContent = documentToContent;
|
|
52028
54865
|
exports.editFile = editFile;
|
|
54866
|
+
exports.encodeSharingUrl = encodeSharingUrl;
|
|
52029
54867
|
exports.evaluateCondition = evaluateCondition;
|
|
52030
54868
|
exports.executeRoutine = executeRoutine;
|
|
52031
54869
|
exports.extractJSON = extractJSON;
|
|
@@ -52035,6 +54873,7 @@ exports.findConnectorByServiceTypes = findConnectorByServiceTypes;
|
|
|
52035
54873
|
exports.forPlan = forPlan;
|
|
52036
54874
|
exports.forTasks = forTasks;
|
|
52037
54875
|
exports.formatAttendees = formatAttendees;
|
|
54876
|
+
exports.formatFileSize = formatFileSize;
|
|
52038
54877
|
exports.formatPluginDisplayName = formatPluginDisplayName;
|
|
52039
54878
|
exports.formatRecipients = formatRecipients;
|
|
52040
54879
|
exports.generateEncryptionKey = generateEncryptionKey;
|
|
@@ -52054,6 +54893,7 @@ exports.getConnectorTools = getConnectorTools;
|
|
|
52054
54893
|
exports.getCredentialsSetupURL = getCredentialsSetupURL;
|
|
52055
54894
|
exports.getDesktopDriver = getDesktopDriver;
|
|
52056
54895
|
exports.getDocsURL = getDocsURL;
|
|
54896
|
+
exports.getDrivePrefix = getDrivePrefix;
|
|
52057
54897
|
exports.getImageModelInfo = getImageModelInfo;
|
|
52058
54898
|
exports.getImageModelsByVendor = getImageModelsByVendor;
|
|
52059
54899
|
exports.getImageModelsWithFeature = getImageModelsWithFeature;
|
|
@@ -52103,6 +54943,7 @@ exports.isBlockedCommand = isBlockedCommand;
|
|
|
52103
54943
|
exports.isErrorEvent = isErrorEvent;
|
|
52104
54944
|
exports.isExcludedExtension = isExcludedExtension;
|
|
52105
54945
|
exports.isKnownService = isKnownService;
|
|
54946
|
+
exports.isMicrosoftFileUrl = isMicrosoftFileUrl;
|
|
52106
54947
|
exports.isOutputTextDelta = isOutputTextDelta;
|
|
52107
54948
|
exports.isReasoningDelta = isReasoningDelta;
|
|
52108
54949
|
exports.isReasoningDone = isReasoningDone;
|
|
@@ -52118,6 +54959,7 @@ exports.isToolCallArgumentsDelta = isToolCallArgumentsDelta;
|
|
|
52118
54959
|
exports.isToolCallArgumentsDone = isToolCallArgumentsDone;
|
|
52119
54960
|
exports.isToolCallStart = isToolCallStart;
|
|
52120
54961
|
exports.isVendor = isVendor;
|
|
54962
|
+
exports.isWebUrl = isWebUrl;
|
|
52121
54963
|
exports.killBackgroundProcess = killBackgroundProcess;
|
|
52122
54964
|
exports.listConnectorsByServiceTypes = listConnectorsByServiceTypes;
|
|
52123
54965
|
exports.listDirectory = listDirectory;
|
|
@@ -52138,6 +54980,8 @@ exports.registerScrapeProvider = registerScrapeProvider;
|
|
|
52138
54980
|
exports.resetDefaultDriver = resetDefaultDriver;
|
|
52139
54981
|
exports.resolveConnector = resolveConnector;
|
|
52140
54982
|
exports.resolveDependencies = resolveDependencies;
|
|
54983
|
+
exports.resolveFileEndpoints = resolveFileEndpoints;
|
|
54984
|
+
exports.resolveFlowSource = resolveFlowSource;
|
|
52141
54985
|
exports.resolveMaxContextTokens = resolveMaxContextTokens;
|
|
52142
54986
|
exports.resolveMeetingId = resolveMeetingId;
|
|
52143
54987
|
exports.resolveModelCapabilities = resolveModelCapabilities;
|