@nuvin/nuvin-core 1.5.1 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -33,7 +33,8 @@ var AgentEventTypes = {
33
33
  SubAgentStarted: "sub_agent_started",
34
34
  SubAgentToolCall: "sub_agent_tool_call",
35
35
  SubAgentToolResult: "sub_agent_tool_result",
36
- SubAgentCompleted: "sub_agent_completed"
36
+ SubAgentCompleted: "sub_agent_completed",
37
+ SubAgentMetrics: "sub_agent_metrics"
37
38
  };
38
39
 
39
40
  // src/metrics.ts
@@ -375,6 +376,391 @@ var PersistingConsoleEventPort = class {
375
376
  }
376
377
  };
377
378
 
379
+ // src/tools/tool-call-parser.ts
380
+ function parseJSON(jsonString) {
381
+ try {
382
+ const parsed = JSON.parse(jsonString || "{}");
383
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
384
+ return {
385
+ success: false,
386
+ error: "Parsed JSON must be an object, not an array or primitive"
387
+ };
388
+ }
389
+ return { success: true, data: parsed };
390
+ } catch (error) {
391
+ return {
392
+ success: false,
393
+ error: error instanceof Error ? error.message : "Invalid JSON syntax"
394
+ };
395
+ }
396
+ }
397
+
398
+ // src/tools/tool-validators.ts
399
+ var validateBashToolParams = (params) => {
400
+ const errors = [];
401
+ if (!params.cmd || typeof params.cmd !== "string") {
402
+ errors.push('Required parameter "cmd" must be a non-empty string');
403
+ }
404
+ if (params.cwd !== void 0 && typeof params.cwd !== "string") {
405
+ errors.push('Parameter "cwd" must be a string');
406
+ }
407
+ if (params.timeoutMs !== void 0) {
408
+ if (typeof params.timeoutMs !== "number" || params.timeoutMs < 1) {
409
+ errors.push('Parameter "timeoutMs" must be a positive number');
410
+ }
411
+ }
412
+ if (params.description !== void 0 && typeof params.description !== "string") {
413
+ errors.push('Parameter "description" must be a string');
414
+ }
415
+ if (errors.length > 0) {
416
+ return { valid: false, errors };
417
+ }
418
+ return {
419
+ valid: true,
420
+ data: {
421
+ cmd: params.cmd,
422
+ cwd: params.cwd,
423
+ timeoutMs: params.timeoutMs,
424
+ description: params.description
425
+ }
426
+ };
427
+ };
428
+ var validateFileReadParams = (params) => {
429
+ const errors = [];
430
+ if (!params.path || typeof params.path !== "string") {
431
+ errors.push('Required parameter "path" must be a non-empty string');
432
+ }
433
+ if (params.lineStart !== void 0) {
434
+ if (typeof params.lineStart !== "number" || params.lineStart < 1) {
435
+ errors.push('Parameter "lineStart" must be a positive integer');
436
+ }
437
+ }
438
+ if (params.lineEnd !== void 0) {
439
+ if (typeof params.lineEnd !== "number" || params.lineEnd < 1) {
440
+ errors.push('Parameter "lineEnd" must be a positive integer');
441
+ }
442
+ }
443
+ if (params.description !== void 0 && typeof params.description !== "string") {
444
+ errors.push('Parameter "description" must be a string');
445
+ }
446
+ if (errors.length > 0) {
447
+ return { valid: false, errors };
448
+ }
449
+ return {
450
+ valid: true,
451
+ data: {
452
+ path: params.path,
453
+ lineStart: params.lineStart,
454
+ lineEnd: params.lineEnd,
455
+ description: params.description
456
+ }
457
+ };
458
+ };
459
+ var validateFileEditParams = (params) => {
460
+ const errors = [];
461
+ if (!params.file_path || typeof params.file_path !== "string") {
462
+ errors.push('Required parameter "file_path" must be a non-empty string');
463
+ }
464
+ if (!params.old_text || typeof params.old_text !== "string") {
465
+ errors.push('Required parameter "old_text" must be a string');
466
+ }
467
+ if (params.new_text === void 0 || typeof params.new_text !== "string") {
468
+ errors.push('Required parameter "new_text" must be a string');
469
+ }
470
+ if (params.dry_run !== void 0 && typeof params.dry_run !== "boolean") {
471
+ errors.push('Parameter "dry_run" must be a boolean');
472
+ }
473
+ if (params.description !== void 0 && typeof params.description !== "string") {
474
+ errors.push('Parameter "description" must be a string');
475
+ }
476
+ if (errors.length > 0) {
477
+ return { valid: false, errors };
478
+ }
479
+ return {
480
+ valid: true,
481
+ data: {
482
+ file_path: params.file_path,
483
+ old_text: params.old_text,
484
+ new_text: params.new_text,
485
+ dry_run: params.dry_run,
486
+ description: params.description
487
+ }
488
+ };
489
+ };
490
+ var validateFileNewParams = (params) => {
491
+ const errors = [];
492
+ if (!params.file_path || typeof params.file_path !== "string") {
493
+ errors.push('Required parameter "file_path" must be a non-empty string');
494
+ }
495
+ if (params.content === void 0 || typeof params.content !== "string") {
496
+ errors.push('Required parameter "content" must be a string');
497
+ }
498
+ if (params.description !== void 0 && typeof params.description !== "string") {
499
+ errors.push('Parameter "description" must be a string');
500
+ }
501
+ if (errors.length > 0) {
502
+ return { valid: false, errors };
503
+ }
504
+ return {
505
+ valid: true,
506
+ data: {
507
+ file_path: params.file_path,
508
+ content: params.content,
509
+ description: params.description
510
+ }
511
+ };
512
+ };
513
+ var validateDirLsParams = (params) => {
514
+ const errors = [];
515
+ if (params.path !== void 0 && typeof params.path !== "string") {
516
+ errors.push('Parameter "path" must be a string');
517
+ }
518
+ if (params.limit !== void 0) {
519
+ if (typeof params.limit !== "number" || params.limit < 1) {
520
+ errors.push('Parameter "limit" must be a positive integer');
521
+ }
522
+ }
523
+ if (params.description !== void 0 && typeof params.description !== "string") {
524
+ errors.push('Parameter "description" must be a string');
525
+ }
526
+ if (errors.length > 0) {
527
+ return { valid: false, errors };
528
+ }
529
+ return {
530
+ valid: true,
531
+ data: {
532
+ path: params.path,
533
+ limit: params.limit,
534
+ description: params.description
535
+ }
536
+ };
537
+ };
538
+ var validateWebSearchParams = (params) => {
539
+ const errors = [];
540
+ if (!params.query || typeof params.query !== "string") {
541
+ errors.push('Required parameter "query" must be a non-empty string');
542
+ }
543
+ if (params.count !== void 0) {
544
+ if (typeof params.count !== "number" || params.count < 1 || params.count > 50) {
545
+ errors.push('Parameter "count" must be a number between 1 and 50');
546
+ }
547
+ }
548
+ if (params.offset !== void 0) {
549
+ if (typeof params.offset !== "number" || params.offset < 0) {
550
+ errors.push('Parameter "offset" must be a non-negative number');
551
+ }
552
+ }
553
+ if (params.domains !== void 0) {
554
+ if (!Array.isArray(params.domains) || !params.domains.every((d) => typeof d === "string")) {
555
+ errors.push('Parameter "domains" must be an array of strings');
556
+ }
557
+ }
558
+ if (params.recencyDays !== void 0) {
559
+ if (typeof params.recencyDays !== "number" || params.recencyDays < 1) {
560
+ errors.push('Parameter "recencyDays" must be a positive number');
561
+ }
562
+ }
563
+ if (params.lang !== void 0 && typeof params.lang !== "string") {
564
+ errors.push('Parameter "lang" must be a string');
565
+ }
566
+ if (params.region !== void 0 && typeof params.region !== "string") {
567
+ errors.push('Parameter "region" must be a string');
568
+ }
569
+ if (params.safe !== void 0 && typeof params.safe !== "boolean") {
570
+ errors.push('Parameter "safe" must be a boolean');
571
+ }
572
+ if (params.type !== void 0) {
573
+ if (params.type !== "web" && params.type !== "images") {
574
+ errors.push('Parameter "type" must be either "web" or "images"');
575
+ }
576
+ }
577
+ if (params.hydrateMeta !== void 0 && typeof params.hydrateMeta !== "boolean") {
578
+ errors.push('Parameter "hydrateMeta" must be a boolean');
579
+ }
580
+ if (params.description !== void 0 && typeof params.description !== "string") {
581
+ errors.push('Parameter "description" must be a string');
582
+ }
583
+ if (errors.length > 0) {
584
+ return { valid: false, errors };
585
+ }
586
+ return {
587
+ valid: true,
588
+ data: {
589
+ query: params.query,
590
+ count: params.count,
591
+ offset: params.offset,
592
+ domains: params.domains,
593
+ recencyDays: params.recencyDays,
594
+ lang: params.lang,
595
+ region: params.region,
596
+ safe: params.safe,
597
+ type: params.type,
598
+ hydrateMeta: params.hydrateMeta,
599
+ description: params.description
600
+ }
601
+ };
602
+ };
603
+ var validateWebFetchParams = (params) => {
604
+ const errors = [];
605
+ if (!params.url || typeof params.url !== "string") {
606
+ errors.push('Required parameter "url" must be a non-empty string');
607
+ }
608
+ if (params.description !== void 0 && typeof params.description !== "string") {
609
+ errors.push('Parameter "description" must be a string');
610
+ }
611
+ if (errors.length > 0) {
612
+ return { valid: false, errors };
613
+ }
614
+ return {
615
+ valid: true,
616
+ data: {
617
+ url: params.url,
618
+ description: params.description
619
+ }
620
+ };
621
+ };
622
+ var validateTodoWriteParams = (params) => {
623
+ const errors = [];
624
+ if (!params.todos || !Array.isArray(params.todos)) {
625
+ errors.push('Required parameter "todos" must be an array');
626
+ } else {
627
+ for (let i = 0; i < params.todos.length; i++) {
628
+ const todo = params.todos[i];
629
+ if (typeof todo !== "object" || todo === null) {
630
+ errors.push(`todos[${i}] must be an object`);
631
+ continue;
632
+ }
633
+ if (!todo.id || typeof todo.id !== "string") {
634
+ errors.push(`todos[${i}].id must be a non-empty string`);
635
+ }
636
+ if (!todo.content || typeof todo.content !== "string") {
637
+ errors.push(`todos[${i}].content must be a non-empty string`);
638
+ }
639
+ if (!todo.status || !["pending", "in_progress", "completed"].includes(todo.status)) {
640
+ errors.push(`todos[${i}].status must be one of: pending, in_progress, completed`);
641
+ }
642
+ if (!todo.priority || !["high", "medium", "low"].includes(todo.priority)) {
643
+ errors.push(`todos[${i}].priority must be one of: high, medium, low`);
644
+ }
645
+ if (todo.createdAt !== void 0 && typeof todo.createdAt !== "string") {
646
+ errors.push(`todos[${i}].createdAt must be a string`);
647
+ }
648
+ }
649
+ }
650
+ if (params.description !== void 0 && typeof params.description !== "string") {
651
+ errors.push('Parameter "description" must be a string');
652
+ }
653
+ if (errors.length > 0) {
654
+ return { valid: false, errors };
655
+ }
656
+ return {
657
+ valid: true,
658
+ data: {
659
+ todos: params.todos,
660
+ description: params.description
661
+ }
662
+ };
663
+ };
664
+ var validateAssignTaskParams = (params) => {
665
+ const errors = [];
666
+ if (!params.agent || typeof params.agent !== "string") {
667
+ errors.push('Required parameter "agent" must be a non-empty string');
668
+ }
669
+ if (!params.task || typeof params.task !== "string") {
670
+ errors.push('Required parameter "task" must be a non-empty string');
671
+ }
672
+ if (!params.description || typeof params.description !== "string") {
673
+ errors.push('Required parameter "description" must be a non-empty string');
674
+ }
675
+ if (errors.length > 0) {
676
+ return { valid: false, errors };
677
+ }
678
+ return {
679
+ valid: true,
680
+ data: {
681
+ agent: params.agent,
682
+ task: params.task,
683
+ description: params.description
684
+ }
685
+ };
686
+ };
687
+ var toolValidators = {
688
+ bash_tool: validateBashToolParams,
689
+ file_read: validateFileReadParams,
690
+ file_edit: validateFileEditParams,
691
+ file_new: validateFileNewParams,
692
+ dir_ls: validateDirLsParams,
693
+ web_search: validateWebSearchParams,
694
+ web_fetch: validateWebFetchParams,
695
+ todo_write: validateTodoWriteParams,
696
+ assign_task: validateAssignTaskParams
697
+ };
698
+
699
+ // src/tools/tool-call-converter.ts
700
+ function convertToolCall(toolCall, options = {}) {
701
+ const { strict = false } = options;
702
+ const parseResult = parseJSON(toolCall.function.arguments);
703
+ if (!parseResult.success) {
704
+ return {
705
+ valid: false,
706
+ error: parseResult.error,
707
+ errorType: "parse",
708
+ callId: toolCall.id,
709
+ toolName: toolCall.function.name,
710
+ rawArguments: toolCall.function.arguments
711
+ };
712
+ }
713
+ const toolName = toolCall.function.name;
714
+ const parameters = parseResult.data;
715
+ const validator = toolValidators[toolName];
716
+ if (validator) {
717
+ const validationResult = validator(parameters);
718
+ if (!validationResult.valid) {
719
+ if (strict) {
720
+ return {
721
+ valid: false,
722
+ error: `Validation failed: ${validationResult.errors.join("; ")}`,
723
+ errorType: "validation",
724
+ callId: toolCall.id,
725
+ toolName: toolCall.function.name,
726
+ rawArguments: toolCall.function.arguments
727
+ };
728
+ }
729
+ console.warn(`[ToolCallConverter] Validation warnings for ${toolName}:`, validationResult.errors);
730
+ }
731
+ }
732
+ return {
733
+ valid: true,
734
+ invocation: {
735
+ id: toolCall.id,
736
+ name: toolCall.function.name,
737
+ parameters
738
+ }
739
+ };
740
+ }
741
+ function convertToolCalls(toolCalls, options = {}) {
742
+ const { strict = false, throwOnError = true } = options;
743
+ const invocations = [];
744
+ for (const tc of toolCalls) {
745
+ const result = convertToolCall(tc, { strict });
746
+ if (!result.valid) {
747
+ const errorMsg = `Tool call ${result.callId} (${result.toolName}): ${result.error}`;
748
+ if (throwOnError) {
749
+ throw new Error(errorMsg);
750
+ }
751
+ console.error(`[ToolCallConverter] ${errorMsg}`);
752
+ invocations.push({
753
+ id: result.callId,
754
+ name: result.toolName,
755
+ parameters: {}
756
+ });
757
+ } else {
758
+ invocations.push(result.invocation);
759
+ }
760
+ }
761
+ return invocations;
762
+ }
763
+
378
764
  // src/orchestrator.ts
379
765
  var removeAttachmentTokens = (value, attachments) => {
380
766
  return attachments.reduce((acc, attachment) => {
@@ -812,7 +1198,14 @@ var AgentOrchestrator = class {
812
1198
  };
813
1199
  const toolResultMsgs = [];
814
1200
  for (const tr of toolResults) {
815
- const contentStr = tr.status === "error" ? String(tr.result) : typeof tr.result === "string" ? tr.result : JSON.stringify(tr.result);
1201
+ let contentStr;
1202
+ if (tr.status === "error") {
1203
+ contentStr = tr.result;
1204
+ } else if (tr.type === "text") {
1205
+ contentStr = tr.result;
1206
+ } else {
1207
+ contentStr = JSON.stringify(tr.result, null, 2);
1208
+ }
816
1209
  toolResultMsgs.push({
817
1210
  id: tr.id,
818
1211
  role: "tool",
@@ -838,7 +1231,14 @@ var AgentOrchestrator = class {
838
1231
  tool_calls: approvedCalls
839
1232
  });
840
1233
  for (const tr of toolResults) {
841
- const contentStr = tr.status === "error" ? String(tr.result) : typeof tr.result === "string" ? tr.result : JSON.stringify(tr.result);
1234
+ let contentStr;
1235
+ if (tr.status === "error") {
1236
+ contentStr = tr.result;
1237
+ } else if (tr.type === "text") {
1238
+ contentStr = tr.result;
1239
+ } else {
1240
+ contentStr = JSON.stringify(tr.result, null, 2);
1241
+ }
842
1242
  accumulatedMessages.push({ role: "tool", content: contentStr, tool_call_id: tr.id, name: tr.name });
843
1243
  }
844
1244
  if (opts.signal?.aborted) throw new Error("Aborted");
@@ -978,14 +1378,9 @@ var AgentOrchestrator = class {
978
1378
  }
979
1379
  }
980
1380
  toInvocations(toolCalls) {
981
- return toolCalls.map((tc) => {
982
- let parameters = {};
983
- try {
984
- parameters = JSON.parse(tc.function.arguments || "{}");
985
- } catch {
986
- parameters = {};
987
- }
988
- return { id: tc.id, name: tc.function.name, parameters };
1381
+ return convertToolCalls(toolCalls, {
1382
+ strict: this.cfg.strictToolValidation ?? false,
1383
+ throwOnError: true
989
1384
  });
990
1385
  }
991
1386
  };
@@ -1543,6 +1938,18 @@ var TodoStore = class {
1543
1938
  }
1544
1939
  };
1545
1940
 
1941
+ // src/tools/result-helpers.ts
1942
+ function okText(result, metadata) {
1943
+ return { status: "success", type: "text", result, metadata };
1944
+ }
1945
+ function okJson(result, metadata) {
1946
+ return { status: "success", type: "json", result, metadata };
1947
+ }
1948
+ function err(result, metadata, errorReason) {
1949
+ const finalMetadata = errorReason ? { ...metadata, errorReason } : metadata;
1950
+ return { status: "error", type: "text", result, metadata: finalMetadata };
1951
+ }
1952
+
1546
1953
  // src/tools/TodoWriteTool.ts
1547
1954
  var TodoWriteTool = class {
1548
1955
  constructor(store) {
@@ -1634,28 +2041,28 @@ When in doubt, use this tool. Being proactive with task management demonstrates
1634
2041
  const todos = params?.todos;
1635
2042
  const ctx = context;
1636
2043
  if (!Array.isArray(todos) || todos.length === 0) {
1637
- return { status: "error", type: "text", result: 'Parameter "todos" must be a non-empty array' };
2044
+ return err('Parameter "todos" must be a non-empty array', void 0, "invalid_input" /* InvalidInput */);
1638
2045
  }
1639
2046
  for (let i = 0; i < todos.length; i++) {
1640
2047
  const t = todos[i];
1641
2048
  if (!t) continue;
1642
2049
  if (!t.content || typeof t.content !== "string" || !t.content.trim()) {
1643
- return { status: "error", type: "text", result: `Todo ${i + 1}: content required` };
2050
+ return err(`Todo ${i + 1}: content required`, void 0, "invalid_input" /* InvalidInput */);
1644
2051
  }
1645
2052
  if (!["pending", "in_progress", "completed"].includes(String(t.status))) {
1646
- return { status: "error", type: "text", result: `Todo ${i + 1}: invalid status` };
2053
+ return err(`Todo ${i + 1}: invalid status`, void 0, "invalid_input" /* InvalidInput */);
1647
2054
  }
1648
2055
  if (!["high", "medium", "low"].includes(String(t.priority))) {
1649
- return { status: "error", type: "text", result: `Todo ${i + 1}: invalid priority` };
2056
+ return err(`Todo ${i + 1}: invalid priority`, void 0, "invalid_input" /* InvalidInput */);
1650
2057
  }
1651
2058
  if (!t.id || typeof t.id !== "string" || !t.id.trim()) {
1652
- return { status: "error", type: "text", result: `Todo ${i + 1}: id required` };
2059
+ return err(`Todo ${i + 1}: id required`, void 0, "invalid_input" /* InvalidInput */);
1653
2060
  }
1654
2061
  }
1655
2062
  const ids = todos.map((t) => t.id);
1656
2063
  const unique = new Set(ids);
1657
2064
  if (unique.size !== ids.length) {
1658
- return { status: "error", type: "text", result: "Todo items must have unique IDs" };
2065
+ return err("Todo items must have unique IDs", void 0, "invalid_input" /* InvalidInput */);
1659
2066
  }
1660
2067
  const conversationId = ctx?.sessionId || ctx?.conversationId || "default";
1661
2068
  const nowIso = (/* @__PURE__ */ new Date()).toISOString();
@@ -1694,17 +2101,13 @@ All todo items are now completed! Ask about next steps. Completed list:
1694
2101
  Your todo list has changed. DO NOT mention this explicitly to the user. Latest contents:
1695
2102
  [${serialized}].
1696
2103
  </system-reminder>`;
1697
- return {
1698
- status: "success",
1699
- type: "text",
1700
- result: systemReminder,
1701
- metadata: {
1702
- recentChanges: true,
1703
- stats,
1704
- progress: `${progress}%`,
1705
- allCompleted
1706
- }
1707
- };
2104
+ return okText(systemReminder, {
2105
+ recentChanges: true,
2106
+ stats,
2107
+ progress: `${progress}%`,
2108
+ allCompleted,
2109
+ conversationId
2110
+ });
1708
2111
  }
1709
2112
  };
1710
2113
 
@@ -1919,21 +2322,35 @@ var WebSearchTool = class {
1919
2322
  };
1920
2323
  }
1921
2324
  provider = new GoogleCseProvider();
2325
+ /**
2326
+ * Execute web search using Google Programmable Search Engine
2327
+ *
2328
+ * @param params - Search query and optional filters (domains, recency, language, region)
2329
+ * @param ctx - Execution context with optional abort signal
2330
+ * @returns Structured JSON with search results including title, URL, snippet, and metadata
2331
+ *
2332
+ * @example
2333
+ * ```typescript
2334
+ * const result = await webSearchTool.execute({
2335
+ * query: 'TypeScript discriminated unions',
2336
+ * count: 10,
2337
+ * lang: 'en'
2338
+ * });
2339
+ * if (result.status === 'success' && result.type === 'json') {
2340
+ * console.log(result.result.count); // number of results
2341
+ * result.result.items.forEach(item => {
2342
+ * console.log(`${item.title}: ${item.url}`);
2343
+ * });
2344
+ * }
2345
+ * ```
2346
+ */
1922
2347
  async execute(params, ctx) {
1923
2348
  if (ctx?.signal?.aborted) {
1924
- return {
1925
- type: "text",
1926
- status: "error",
1927
- result: "Search aborted by user"
1928
- };
2349
+ return err("Search aborted by user", void 0, "aborted" /* Aborted */);
1929
2350
  }
1930
2351
  const query = typeof params.query === "string" ? params.query : "";
1931
2352
  if (!query) {
1932
- return {
1933
- type: "text",
1934
- status: "error",
1935
- result: "Missing required parameter: query"
1936
- };
2353
+ return err("Missing required parameter: query", void 0, "invalid_input" /* InvalidInput */);
1937
2354
  }
1938
2355
  const p = {
1939
2356
  query: String(params.query ?? "").trim(),
@@ -1948,14 +2365,10 @@ var WebSearchTool = class {
1948
2365
  hydrateMeta: params.hydrateMeta ?? false
1949
2366
  };
1950
2367
  if (!p.query) {
1951
- return { status: "error", type: "text", result: "Missing 'query'" };
2368
+ return err("Missing 'query'", void 0, "invalid_input" /* InvalidInput */);
1952
2369
  }
1953
2370
  if (!process.env.GOOGLE_CSE_KEY || !process.env.GOOGLE_CSE_CX) {
1954
- return {
1955
- status: "error",
1956
- type: "text",
1957
- result: "GOOGLE_CSE_KEY and GOOGLE_CSE_CX are required for this tool (Google CSE only)."
1958
- };
2371
+ return err("GOOGLE_CSE_KEY and GOOGLE_CSE_CX are required for this tool (Google CSE only).", void 0, "invalid_input" /* InvalidInput */);
1959
2372
  }
1960
2373
  try {
1961
2374
  let results = await this.provider.search(p, ctx?.signal);
@@ -1969,23 +2382,30 @@ var WebSearchTool = class {
1969
2382
  seen.add(k);
1970
2383
  return true;
1971
2384
  }).slice(0, p.count);
1972
- return {
1973
- status: "success",
1974
- type: "json",
1975
- result: {
1976
- engine: this.provider.name,
1977
- count: results.length,
1978
- items: results
1979
- }
1980
- };
2385
+ const appliedFilters = {};
2386
+ if (p.domains.length > 0) appliedFilters.domains = p.domains;
2387
+ if (p.recencyDays > 0) appliedFilters.recencyDays = p.recencyDays;
2388
+ if (p.lang && p.lang !== "en") appliedFilters.lang = p.lang;
2389
+ if (p.region) appliedFilters.region = p.region;
2390
+ return okJson({
2391
+ engine: this.provider.name,
2392
+ count: results.length,
2393
+ items: results,
2394
+ query: p.query,
2395
+ appliedFilters: Object.keys(appliedFilters).length > 0 ? appliedFilters : void 0
2396
+ }, {
2397
+ offset: p.offset,
2398
+ totalRequested: p.count,
2399
+ hydrated: p.hydrateMeta
2400
+ });
1981
2401
  } catch (error) {
1982
2402
  const message = error instanceof Error ? error.message : String(error);
1983
2403
  const isAborted = message.includes("aborted by user");
1984
- return {
1985
- status: "error",
1986
- type: "text",
1987
- result: isAborted ? "Search aborted by user" : message.slice(0, 500)
1988
- };
2404
+ return err(
2405
+ isAborted ? "Search aborted by user" : message.slice(0, 500),
2406
+ void 0,
2407
+ isAborted ? "aborted" /* Aborted */ : "unknown" /* Unknown */
2408
+ );
1989
2409
  }
1990
2410
  }
1991
2411
  };
@@ -2024,10 +2444,10 @@ var WebFetchTool = class {
2024
2444
  async execute(params, ctx) {
2025
2445
  const target = String(params.url ?? "");
2026
2446
  if (!target) {
2027
- return { status: "error", type: "text", result: "URL parameter is required" };
2447
+ return err("URL parameter is required", void 0, "invalid_input" /* InvalidInput */);
2028
2448
  }
2029
2449
  if (ctx?.signal?.aborted) {
2030
- return { status: "error", type: "text", result: "Fetch aborted by user" };
2450
+ return err("Fetch aborted by user", void 0, "aborted" /* Aborted */);
2031
2451
  }
2032
2452
  try {
2033
2453
  const res = await fetch(target, {
@@ -2037,33 +2457,41 @@ var WebFetchTool = class {
2037
2457
  signal: ctx?.signal
2038
2458
  });
2039
2459
  if (!res.ok) {
2040
- return {
2041
- status: "error",
2042
- type: "text",
2043
- result: `HTTP ${res.status} ${res.statusText}`
2044
- };
2460
+ return err(`HTTP ${res.status} ${res.statusText}`, { url: target }, "network_error" /* NetworkError */);
2045
2461
  }
2046
2462
  const contentType = res.headers.get("content-type") || "";
2047
2463
  const text = await res.text();
2464
+ const fetchedAt = (/* @__PURE__ */ new Date()).toISOString();
2465
+ let finalText;
2466
+ let format;
2048
2467
  if (contentType.includes("application/json")) {
2049
- const markdown = `\`\`\`json
2468
+ finalText = `\`\`\`json
2050
2469
  ${text}
2051
2470
  \`\`\``;
2052
- return { status: "success", type: "text", result: markdown };
2471
+ format = "json";
2053
2472
  } else if (contentType.includes("text/html")) {
2054
- const markdown = this.turndownService.turndown(text);
2055
- return { status: "success", type: "text", result: markdown };
2473
+ finalText = this.turndownService.turndown(text);
2474
+ format = "markdown";
2056
2475
  } else {
2057
- return { status: "success", type: "text", result: text };
2476
+ finalText = text;
2477
+ format = "text";
2058
2478
  }
2479
+ return okText(finalText, {
2480
+ url: target,
2481
+ contentType,
2482
+ statusCode: res.status,
2483
+ format,
2484
+ size: text.length,
2485
+ fetchedAt
2486
+ });
2059
2487
  } catch (error) {
2060
2488
  const errorMessage = error instanceof Error ? error.message : String(error);
2061
2489
  const isAborted = errorMessage.includes("aborted") || error.name === "AbortError";
2062
- return {
2063
- status: "error",
2064
- type: "text",
2065
- result: isAborted ? "Fetch aborted by user" : errorMessage
2066
- };
2490
+ return err(
2491
+ isAborted ? "Fetch aborted by user" : errorMessage,
2492
+ { url: target },
2493
+ isAborted ? "aborted" /* Aborted */ : "network_error" /* NetworkError */
2494
+ );
2067
2495
  }
2068
2496
  }
2069
2497
  };
@@ -2071,17 +2499,6 @@ ${text}
2071
2499
  // src/tools/FileReadTool.ts
2072
2500
  import { promises as fs3 } from "fs";
2073
2501
  import * as path3 from "path";
2074
-
2075
- // src/tools/result-helpers.ts
2076
- function ok(result, metadata) {
2077
- return { status: "success", type: "text", result, metadata };
2078
- }
2079
- function err(result, metadata, errorReason) {
2080
- const finalMetadata = errorReason ? { ...metadata, errorReason } : metadata;
2081
- return { status: "error", type: "text", result, metadata: finalMetadata };
2082
- }
2083
-
2084
- // src/tools/FileReadTool.ts
2085
2502
  var FileReadTool = class {
2086
2503
  name = "file_read";
2087
2504
  rootDir;
@@ -2114,24 +2531,45 @@ var FileReadTool = class {
2114
2531
  parameters: this.parameters
2115
2532
  };
2116
2533
  }
2534
+ /**
2535
+ * Read file contents with optional line range selection
2536
+ *
2537
+ * @param params - File path and optional line range (lineStart, lineEnd)
2538
+ * @param context - Execution context with optional workspace directory
2539
+ * @returns File content as text with metadata including path, size, and line range
2540
+ *
2541
+ * @example
2542
+ * ```typescript
2543
+ * const result = await fileReadTool.execute({ path: 'package.json' });
2544
+ * if (result.status === 'success' && result.type === 'text') {
2545
+ * console.log(result.result); // file contents
2546
+ * console.log(result.metadata?.path); // resolved file path
2547
+ * console.log(result.metadata?.size); // file size in bytes
2548
+ * }
2549
+ * ```
2550
+ */
2117
2551
  async execute(params, context) {
2118
2552
  try {
2119
2553
  if (!params.path || typeof params.path !== "string") {
2120
- return err('Parameter "path" must be a non-empty string');
2554
+ return err('Parameter "path" must be a non-empty string', void 0, "invalid_input" /* InvalidInput */);
2121
2555
  }
2122
2556
  const abs = this.resolveSafePath(params.path, context);
2123
2557
  const st = await fs3.stat(abs).catch(() => null);
2124
- if (!st || !st.isFile()) return err(`File not found: ${params.path}`);
2558
+ if (!st || !st.isFile()) return err(`File not found: ${params.path}`, { path: params.path }, "not_found" /* NotFound */);
2125
2559
  if (st.size > this.maxBytesHard) {
2126
- return err(`File too large (${st.size} bytes). Hard cap is ${this.maxBytesHard} bytes.`);
2560
+ return err(`File too large (${st.size} bytes). Hard cap is ${this.maxBytesHard} bytes.`, { path: params.path }, "invalid_input" /* InvalidInput */);
2127
2561
  }
2128
2562
  const payload = await fs3.readFile(abs);
2129
2563
  let text = payload.toString("utf8");
2564
+ const bomStripped = text.charCodeAt(0) === 65279;
2130
2565
  text = stripUtfBom(text);
2131
2566
  const metadata = {
2132
2567
  path: params.path,
2133
2568
  created: st.birthtime.toISOString(),
2134
- modified: st.mtime.toISOString()
2569
+ modified: st.mtime.toISOString(),
2570
+ size: st.size,
2571
+ encoding: "utf8",
2572
+ bomStripped
2135
2573
  };
2136
2574
  if (params.lineStart || params.lineEnd) {
2137
2575
  const lines = text.split(/\r?\n/);
@@ -2144,17 +2582,19 @@ var FileReadTool = class {
2144
2582
  return `${lineNum}\u2502${line}`;
2145
2583
  });
2146
2584
  const slice = numberedLines.join("\n");
2147
- return ok(slice, {
2585
+ return okText(slice, {
2148
2586
  ...metadata,
2149
- linesTotal: totalLines,
2150
- lineStart: lo,
2151
- lineEnd: hi
2587
+ lineRange: {
2588
+ lineStart: lo,
2589
+ lineEnd: hi,
2590
+ linesTotal: totalLines
2591
+ }
2152
2592
  });
2153
2593
  }
2154
- return ok(text, metadata);
2594
+ return okText(text, metadata);
2155
2595
  } catch (e) {
2156
2596
  const message = e instanceof Error ? e.message : String(e);
2157
- return err(message);
2597
+ return err(message, { path: params.path }, "unknown" /* Unknown */);
2158
2598
  }
2159
2599
  }
2160
2600
  // ---------- helpers ----------
@@ -2209,16 +2649,22 @@ var FileNewTool = class {
2209
2649
  }
2210
2650
  async execute(p, ctx) {
2211
2651
  try {
2212
- if (!p.file_path?.trim()) return err('Parameter "file_path" is required.');
2213
- if (typeof p.content !== "string") return err('"content" must be a string.');
2652
+ if (!p.file_path?.trim()) return err('Parameter "file_path" is required.', void 0, "invalid_input" /* InvalidInput */);
2653
+ if (typeof p.content !== "string") return err('"content" must be a string.', void 0, "invalid_input" /* InvalidInput */);
2214
2654
  const abs = this.resolveSafePath(p.file_path, ctx);
2655
+ const existsBefore = await fs4.stat(abs).then(() => true).catch(() => false);
2215
2656
  await fs4.mkdir(path4.dirname(abs), { recursive: true });
2216
2657
  const bytes = Buffer.from(p.content, "utf8");
2217
2658
  await this.writeAtomic(abs, bytes);
2218
- return ok(`File written at ${p.file_path}.`, { file_path: p.file_path, bytes: bytes.length });
2659
+ return okText(`File written at ${p.file_path}.`, {
2660
+ file_path: p.file_path,
2661
+ bytes: bytes.length,
2662
+ created: (/* @__PURE__ */ new Date()).toISOString(),
2663
+ overwritten: existsBefore
2664
+ });
2219
2665
  } catch (e) {
2220
2666
  const message = e instanceof Error ? e.message : String(e);
2221
- return err(message);
2667
+ return err(message, void 0, "unknown" /* Unknown */);
2222
2668
  }
2223
2669
  }
2224
2670
  // helpers
@@ -2295,21 +2741,21 @@ var FileEditTool = class {
2295
2741
  }
2296
2742
  async execute(p, ctx) {
2297
2743
  try {
2298
- if (!p.file_path?.trim()) return err('Parameter "file_path" is required.');
2744
+ if (!p.file_path?.trim()) return err('Parameter "file_path" is required.', void 0, "invalid_input" /* InvalidInput */);
2299
2745
  const abs = resolveAbsoluteWithinRoot(p.file_path, ctx, this.rootDir);
2300
2746
  if (typeof p.old_text !== "string") {
2301
- return err("old_text must be a string.");
2747
+ return err("old_text must be a string.", void 0, "invalid_input" /* InvalidInput */);
2302
2748
  }
2303
2749
  if (typeof p.new_text !== "string") {
2304
- return err("new_text must be a string.");
2750
+ return err("new_text must be a string.", void 0, "invalid_input" /* InvalidInput */);
2305
2751
  }
2306
2752
  const st = await fs5.stat(abs).catch(() => null);
2307
2753
  const exists = !!st && st.isFile();
2308
2754
  if (!exists || !st) {
2309
- return err("File does not exist. Use a file creation tool to create new files.");
2755
+ return err("File does not exist. Use a file creation tool to create new files.", void 0, "not_found" /* NotFound */);
2310
2756
  }
2311
2757
  if (st.size > this.maxBytes) {
2312
- return err(`File too large (${st.size} bytes). Cap is ${this.maxBytes}.`);
2758
+ return err(`File too large (${st.size} bytes). Cap is ${this.maxBytes}.`, void 0, "invalid_input" /* InvalidInput */);
2313
2759
  }
2314
2760
  const originalBytes = await fs5.readFile(abs);
2315
2761
  const originalEol = detectEol(originalBytes) ?? "lf";
@@ -2322,14 +2768,16 @@ var FileEditTool = class {
2322
2768
  const preview = oldTextLF.slice(0, 100).replace(/\n/g, "\\n");
2323
2769
  return err(
2324
2770
  `old_text not found in file. Make sure it matches exactly including whitespace.
2325
- Searching for: "${preview}${oldTextLF.length > 100 ? "..." : ""}"`
2771
+ Searching for: "${preview}${oldTextLF.length > 100 ? "..." : ""}"`,
2772
+ void 0,
2773
+ "not_found" /* NotFound */
2326
2774
  );
2327
2775
  }
2328
2776
  const resultLF = originalLF.slice(0, idx) + newTextLF + originalLF.slice(idx + oldTextLF.length);
2329
2777
  const finalText = convertEol(resultLF, originalEol);
2330
2778
  const finalBytes = Buffer.from(finalText, "utf8");
2331
2779
  if (finalBytes.length > this.maxBytes) {
2332
- return err(`Resulting file too large (${finalBytes.length} bytes). Cap is ${this.maxBytes}.`);
2780
+ return err(`Resulting file too large (${finalBytes.length} bytes). Cap is ${this.maxBytes}.`, void 0, "invalid_input" /* InvalidInput */);
2333
2781
  }
2334
2782
  const beforeSha = sha256(originalBytes);
2335
2783
  const afterSha = sha256(finalBytes);
@@ -2338,10 +2786,13 @@ Searching for: "${preview}${oldTextLF.length > 100 ? "..." : ""}"`
2338
2786
  await atomicWrite(abs, finalBytes);
2339
2787
  }
2340
2788
  const lineNumbers = this.calculateLineNumbers(originalLF, oldTextLF, newTextLF, idx);
2341
- return ok(
2789
+ return okText(
2342
2790
  noChange ? "No changes (content identical)." : p.dry_run ? "Validated (dry run: no write)." : "Edit applied successfully.",
2343
2791
  {
2344
2792
  path: showPath(abs, this.rootDir),
2793
+ created: st.birthtime.toISOString(),
2794
+ modified: st.mtime.toISOString(),
2795
+ size: st.size,
2345
2796
  eol: originalEol,
2346
2797
  oldTextLength: oldTextLF.length,
2347
2798
  newTextLength: newTextLF.length,
@@ -2349,11 +2800,12 @@ Searching for: "${preview}${oldTextLF.length > 100 ? "..." : ""}"`
2349
2800
  beforeSha,
2350
2801
  afterSha,
2351
2802
  dryRun: !!p.dry_run,
2352
- lineNumbers
2803
+ lineNumbers,
2804
+ noChange
2353
2805
  }
2354
2806
  );
2355
2807
  } catch (e) {
2356
- return err(e instanceof Error ? e.message : String(e));
2808
+ return err(e instanceof Error ? e.message : String(e), void 0, "unknown" /* Unknown */);
2357
2809
  }
2358
2810
  }
2359
2811
  calculateLineNumbers(originalText, oldText, newText, matchIndex) {
@@ -2500,6 +2952,23 @@ var BashTool = class {
2500
2952
  parameters: this.parameters
2501
2953
  };
2502
2954
  }
2955
+ /**
2956
+ * Execute bash command with timeout protection
2957
+ *
2958
+ * @param p - Command parameters including cmd, cwd, and optional timeout
2959
+ * @param ctx - Execution context with optional abort signal
2960
+ * @returns Structured result with command output and execution metadata
2961
+ *
2962
+ * @example
2963
+ * ```typescript
2964
+ * const result = await bashTool.execute({ cmd: 'ls -la' });
2965
+ * if (result.status === 'success' && result.type === 'text') {
2966
+ * console.log(result.result); // command output
2967
+ * console.log(result.metadata?.code); // exit code
2968
+ * console.log(result.metadata?.cwd); // working directory
2969
+ * }
2970
+ * ```
2971
+ */
2503
2972
  async execute(p, ctx) {
2504
2973
  try {
2505
2974
  return await this.execOnce(p, ctx?.signal);
@@ -2618,7 +3087,7 @@ ${output}` : "";
2618
3087
  }
2619
3088
  return err(output, metadata);
2620
3089
  }
2621
- return ok(output, { code, signal: exitSignal, cwd });
3090
+ return okText(output, { code, signal: exitSignal, cwd, stripped: stripAnsi });
2622
3091
  } catch (e) {
2623
3092
  if (timer) clearTimeout(timer);
2624
3093
  const message = e instanceof Error ? e.message : String(e);
@@ -2701,7 +3170,7 @@ var DirLsTool = class {
2701
3170
  name: this.name,
2702
3171
  description: [
2703
3172
  "List files and directories in a specified directory.",
2704
- "Returns entries sorted alphabetically by name in ls -la format.",
3173
+ "Returns entries sorted alphabetically by name as structured JSON.",
2705
3174
  "",
2706
3175
  "Typical uses:",
2707
3176
  "1) List all files in current directory",
@@ -2733,6 +3202,25 @@ var DirLsTool = class {
2733
3202
  return 0;
2734
3203
  }
2735
3204
  }
3205
+ /**
3206
+ * List directory contents as structured JSON
3207
+ *
3208
+ * @param params - Directory path and optional limit
3209
+ * @param context - Execution context with optional workspace directory
3210
+ * @returns Structured JSON with directory entries including name, type, size, and metadata
3211
+ *
3212
+ * @example
3213
+ * ```typescript
3214
+ * const result = await dirLsTool.execute({ path: 'src/tools', limit: 50 });
3215
+ * if (result.status === 'success' && result.type === 'json') {
3216
+ * console.log(result.result.path); // 'src/tools'
3217
+ * console.log(result.result.total); // number of entries
3218
+ * result.result.entries.forEach(entry => {
3219
+ * console.log(`${entry.name} (${entry.type}): ${entry.size} bytes`);
3220
+ * });
3221
+ * }
3222
+ * ```
3223
+ */
2736
3224
  async execute(params, context) {
2737
3225
  try {
2738
3226
  const targetPath = params.path ?? ".";
@@ -2741,10 +3229,10 @@ var DirLsTool = class {
2741
3229
  const abs = this.resolveSafePath(targetPath, context);
2742
3230
  const st = await fs7.stat(abs).catch(() => null);
2743
3231
  if (!st) {
2744
- return err(`Directory not found: ${targetPath}`);
3232
+ return err(`Directory not found: ${targetPath}`, void 0, "not_found" /* NotFound */);
2745
3233
  }
2746
3234
  if (!st.isDirectory()) {
2747
- return err(`Path is not a directory: ${targetPath}`);
3235
+ return err(`Path is not a directory: ${targetPath}`, void 0, "invalid_input" /* InvalidInput */);
2748
3236
  }
2749
3237
  const entries = await fs7.readdir(abs);
2750
3238
  const results = [];
@@ -2781,40 +3269,23 @@ var DirLsTool = class {
2781
3269
  results.push(dirEntry);
2782
3270
  }
2783
3271
  results.sort((a, b) => a.name.localeCompare(b.name));
2784
- const lines = results.map((entry) => this.formatAsLsLine(entry));
2785
- const output = lines.join("\n");
2786
- return ok(output);
3272
+ const result = okJson(
3273
+ {
3274
+ path: targetPath,
3275
+ entries: results,
3276
+ truncated: entries.length > limit,
3277
+ total: results.length
3278
+ },
3279
+ {
3280
+ limit,
3281
+ includeHidden
3282
+ }
3283
+ );
3284
+ return result;
2787
3285
  } catch (e) {
2788
3286
  const message = e instanceof Error ? e.message : String(e);
2789
- return err(message);
2790
- }
2791
- }
2792
- formatAsLsLine(entry) {
2793
- const mode = entry.mode ? this.formatMode(entry.mode) : "----------";
2794
- const size = entry.size !== void 0 ? entry.size.toString().padStart(10) : " 0";
2795
- const date = entry.mtime ? new Date(entry.mtime) : /* @__PURE__ */ new Date();
2796
- const month = date.toLocaleString("en-US", { month: "short" });
2797
- const day = date.getDate().toString().padStart(2);
2798
- const time = `${date.getHours().toString().padStart(2, "0")}:${date.getMinutes().toString().padStart(2, "0")}`;
2799
- const dateStr = `${month} ${day} ${time}`;
2800
- const typeIndicator = entry.type === "directory" ? "/" : entry.type === "symlink" ? "@" : "";
2801
- const name = `${entry.name}${typeIndicator}`;
2802
- return `${mode} ${size} ${dateStr} ${name}`;
2803
- }
2804
- formatMode(mode) {
2805
- const fileType = (mode & 61440) === 16384 ? "d" : (mode & 61440) === 40960 ? "l" : "-";
2806
- const perms = [
2807
- mode & 256 ? "r" : "-",
2808
- mode & 128 ? "w" : "-",
2809
- mode & 64 ? "x" : "-",
2810
- mode & 32 ? "r" : "-",
2811
- mode & 16 ? "w" : "-",
2812
- mode & 8 ? "x" : "-",
2813
- mode & 4 ? "r" : "-",
2814
- mode & 2 ? "w" : "-",
2815
- mode & 1 ? "x" : "-"
2816
- ];
2817
- return fileType + perms.join("");
3287
+ return err(message, void 0, "unknown" /* Unknown */);
3288
+ }
2818
3289
  }
2819
3290
  resolveSafePath(target, context) {
2820
3291
  const baseFromCtx = context?.workspaceRoot || context?.cwd || this.rootDir;
@@ -2892,7 +3363,7 @@ var AgentRegistry = class {
2892
3363
  systemPrompt: partial.systemPrompt,
2893
3364
  tools,
2894
3365
  temperature: partial.temperature ?? 0.7,
2895
- maxTokens: partial.maxTokens ?? 4e3,
3366
+ maxTokens: partial.maxTokens ?? 64e3,
2896
3367
  provider: partial.provider,
2897
3368
  model: partial.model,
2898
3369
  topP: partial.topP,
@@ -3048,7 +3519,7 @@ var AssignTool = class {
3048
3519
  properties: {
3049
3520
  description: {
3050
3521
  type: "string",
3051
- description: 'Explanation of why this task is being delegated (e.g., "Delegate code review to specialist agent")'
3522
+ description: "A summary of the task to be performed by the delegated agent. Be specific about the desired outcome. From 5-10 words."
3052
3523
  },
3053
3524
  agent: {
3054
3525
  type: "string",
@@ -3056,10 +3527,10 @@ var AssignTool = class {
3056
3527
  },
3057
3528
  task: {
3058
3529
  type: "string",
3059
- description: "Task description (3-4 sentences explaining what to do)"
3530
+ description: "Detailed description of the task to be performed by the agent."
3060
3531
  }
3061
3532
  },
3062
- required: ["agent", "task"]
3533
+ required: ["agent", "task", "description"]
3063
3534
  };
3064
3535
  /**
3065
3536
  * Update the enabled agents configuration
@@ -3084,20 +3555,55 @@ ${agentList}`,
3084
3555
  };
3085
3556
  }
3086
3557
  /**
3087
- * Execute task delegation
3558
+ * Delegate a task to a specialist agent for focused execution
3559
+ *
3560
+ * @param params - Agent ID and task description to delegate
3561
+ * @param context - Execution context including delegation depth tracking
3562
+ * @returns Delegation result with comprehensive metrics including cost breakdown
3563
+ *
3564
+ * @example
3565
+ * ```typescript
3566
+ * const result = await assignTool.execute({
3567
+ * agent: 'code-reviewer',
3568
+ * task: 'Review the changes in src/tools/*.ts',
3569
+ * description: 'Code review of tool implementations'
3570
+ * });
3571
+ * if (result.status === 'success' && result.type === 'text') {
3572
+ * console.log(result.result); // Agent's response
3573
+ * console.log(result.metadata.metrics?.totalCost); // Cost in USD
3574
+ * console.log(result.metadata.executionTimeMs); // Duration
3575
+ * console.log(result.metadata.toolCallsExecuted); // Number of tool calls
3576
+ * }
3577
+ * ```
3088
3578
  */
3089
3579
  async execute(params, context) {
3090
3580
  if (!params.agent || typeof params.agent !== "string") {
3091
- return err('Parameter "agent" is required and must be a string');
3581
+ return err('Parameter "agent" is required and must be a string', void 0, "invalid_input" /* InvalidInput */);
3092
3582
  }
3093
3583
  if (!params.task || typeof params.task !== "string") {
3094
- return err('Parameter "task" is required and must be a string');
3584
+ return err('Parameter "task" is required and must be a string', void 0, "invalid_input" /* InvalidInput */);
3095
3585
  }
3096
3586
  const outcome = await this.delegationService.delegate(params, context);
3097
3587
  if (!outcome.success || !outcome.summary) {
3098
- return err(outcome.error ?? "Failed to delegate task.");
3588
+ const isNotFound = outcome.error?.includes("not found");
3589
+ const isPolicyDenied = outcome.error?.includes("policy") || outcome.error?.includes("denied");
3590
+ return err(
3591
+ outcome.error ?? "Failed to delegate task.",
3592
+ {
3593
+ agentId: params.agent,
3594
+ agentNotFound: isNotFound,
3595
+ policyDenied: isPolicyDenied,
3596
+ delegationDepth: context?.delegationDepth
3597
+ },
3598
+ "unknown" /* Unknown */
3599
+ );
3099
3600
  }
3100
- return ok(outcome.summary, outcome.metadata);
3601
+ const metadata = outcome.metadata;
3602
+ return okText(outcome.summary, {
3603
+ ...metadata,
3604
+ taskDescription: params.task,
3605
+ delegationDepth: (context?.delegationDepth ?? 0) + 1
3606
+ });
3101
3607
  }
3102
3608
  };
3103
3609
 
@@ -3196,7 +3702,7 @@ var LLMResolver = class {
3196
3702
  };
3197
3703
 
3198
3704
  // src/agent-manager.ts
3199
- var DEFAULT_TIMEOUT_MS = 3e5;
3705
+ var DEFAULT_TIMEOUT_MS = 3e6;
3200
3706
  var MAX_DELEGATION_DEPTH = 3;
3201
3707
  var AgentManager = class {
3202
3708
  constructor(delegatingConfig, delegatingTools, llmFactory, eventCallback, configResolver) {
@@ -3224,6 +3730,7 @@ var AgentManager = class {
3224
3730
  result: "Sub-agent execution aborted by user",
3225
3731
  metadata: {
3226
3732
  agentId,
3733
+ agentName: config.agentName,
3227
3734
  toolCallsExecuted: 0,
3228
3735
  executionTimeMs: 0,
3229
3736
  errorMessage: "Aborted before execution"
@@ -3237,6 +3744,7 @@ var AgentManager = class {
3237
3744
  result: `Maximum delegation depth (${MAX_DELEGATION_DEPTH}) exceeded`,
3238
3745
  metadata: {
3239
3746
  agentId,
3747
+ agentName: config.agentName,
3240
3748
  toolCallsExecuted: 0,
3241
3749
  executionTimeMs: Date.now() - startTime,
3242
3750
  errorMessage: "Max delegation depth exceeded"
@@ -3300,6 +3808,16 @@ var AgentManager = class {
3300
3808
  // Specialists run autonomously
3301
3809
  reasoningEffort: freshConfig.reasoningEffort ?? this.delegatingConfig.reasoningEffort
3302
3810
  };
3811
+ const metricsPort = new InMemoryMetricsPort((snapshot) => {
3812
+ this.eventCallback?.({
3813
+ type: AgentEventTypes.SubAgentMetrics,
3814
+ conversationId: config.conversationId ?? "default",
3815
+ messageId: config.messageId ?? "",
3816
+ agentId: config.agentId,
3817
+ toolCallId: config.toolCallId ?? "",
3818
+ metrics: snapshot
3819
+ });
3820
+ });
3303
3821
  const llm = this.resolveLLM(config);
3304
3822
  const specialistOrchestrator = new AgentOrchestrator(specialistConfig, {
3305
3823
  memory,
@@ -3310,7 +3828,8 @@ var AgentManager = class {
3310
3828
  clock: new SystemClock(),
3311
3829
  cost: new SimpleCost(),
3312
3830
  reminders: new NoopReminders(),
3313
- events: eventPort
3831
+ events: eventPort,
3832
+ metrics: metricsPort
3314
3833
  });
3315
3834
  this.activeAgents.set(agentId, specialistOrchestrator);
3316
3835
  try {
@@ -3325,6 +3844,7 @@ var AgentManager = class {
3325
3844
  totalTokens += event.usage.total_tokens;
3326
3845
  }
3327
3846
  }
3847
+ const snapshot = metricsPort.getSnapshot();
3328
3848
  this.eventCallback?.({
3329
3849
  type: AgentEventTypes.SubAgentCompleted,
3330
3850
  conversationId: config.conversationId ?? "default",
@@ -3340,11 +3860,13 @@ var AgentManager = class {
3340
3860
  result: response.content || "",
3341
3861
  metadata: {
3342
3862
  agentId,
3863
+ agentName: config.agentName,
3343
3864
  tokensUsed: totalTokens || response.metadata?.totalTokens,
3344
3865
  toolCallsExecuted,
3345
3866
  executionTimeMs,
3346
3867
  conversationHistory,
3347
- events
3868
+ events,
3869
+ metrics: snapshot
3348
3870
  }
3349
3871
  };
3350
3872
  } catch (error) {
@@ -3368,6 +3890,7 @@ var AgentManager = class {
3368
3890
  result: errorMessage,
3369
3891
  metadata: {
3370
3892
  agentId,
3893
+ agentName: config.agentName,
3371
3894
  toolCallsExecuted: events.filter((e) => e.type === "tool_calls").length,
3372
3895
  executionTimeMs,
3373
3896
  errorMessage,
@@ -3394,25 +3917,34 @@ var AgentManager = class {
3394
3917
  * Execute agent task with timeout
3395
3918
  */
3396
3919
  async executeWithTimeout(orchestrator, taskDescription, timeoutMs, signal) {
3397
- return new Promise((resolve6, reject) => {
3398
- const timer = setTimeout(() => {
3399
- reject(new Error(`Task execution timeout after ${timeoutMs}ms`));
3400
- }, timeoutMs);
3401
- const abortHandler = () => {
3402
- clearTimeout(timer);
3403
- reject(new Error("Sub-agent execution aborted by user"));
3404
- };
3405
- signal?.addEventListener("abort", abortHandler);
3406
- orchestrator.send(taskDescription, { conversationId: "default", signal }).then((response) => {
3407
- clearTimeout(timer);
3408
- signal?.removeEventListener("abort", abortHandler);
3409
- resolve6(response);
3410
- }).catch((error) => {
3411
- clearTimeout(timer);
3412
- signal?.removeEventListener("abort", abortHandler);
3413
- reject(error);
3414
- });
3415
- });
3920
+ const timeoutController = new AbortController();
3921
+ const combinedSignal = signal ? AbortSignal.any([signal, timeoutController.signal]) : timeoutController.signal;
3922
+ let timer;
3923
+ try {
3924
+ return await Promise.race([
3925
+ orchestrator.send(taskDescription, {
3926
+ conversationId: "default",
3927
+ signal: combinedSignal
3928
+ }),
3929
+ new Promise((_, reject) => {
3930
+ if (signal?.aborted) {
3931
+ reject(new Error("Sub-agent execution aborted by user"));
3932
+ return;
3933
+ }
3934
+ signal?.addEventListener("abort", () => reject(new Error("Sub-agent execution aborted by user")), {
3935
+ once: true
3936
+ });
3937
+ }),
3938
+ new Promise((_, reject) => {
3939
+ timer = setTimeout(() => {
3940
+ timeoutController.abort();
3941
+ reject(new Error(`Task execution timeout after ${timeoutMs}ms`));
3942
+ }, timeoutMs);
3943
+ })
3944
+ ]);
3945
+ } finally {
3946
+ if (timer) clearTimeout(timer);
3947
+ }
3416
3948
  }
3417
3949
  /**
3418
3950
  * Get active specialist agent count
@@ -3532,10 +4064,12 @@ var DefaultDelegationResultFormatter = class {
3532
4064
  summary: result.result,
3533
4065
  metadata: {
3534
4066
  agentId,
4067
+ agentName: result.metadata.agentName,
3535
4068
  status: result.status,
3536
4069
  executionTimeMs: result.metadata.executionTimeMs,
3537
4070
  toolCallsExecuted: result.metadata.toolCallsExecuted,
3538
- tokensUsed: result.metadata.tokensUsed
4071
+ tokensUsed: result.metadata.tokensUsed,
4072
+ metrics: result.metadata.metrics
3539
4073
  }
3540
4074
  };
3541
4075
  }
@@ -3794,6 +4328,121 @@ var CompositeToolPort = class {
3794
4328
  }
3795
4329
  };
3796
4330
 
4331
+ // src/tools/tool-params.ts
4332
+ function parseToolArguments(args) {
4333
+ if (typeof args === "string") {
4334
+ try {
4335
+ return JSON.parse(args);
4336
+ } catch {
4337
+ return {};
4338
+ }
4339
+ }
4340
+ return args;
4341
+ }
4342
+ function isBashToolArgs(args) {
4343
+ return "cmd" in args && typeof args.cmd === "string";
4344
+ }
4345
+ function isFileReadArgs(args) {
4346
+ return "path" in args && typeof args.path === "string";
4347
+ }
4348
+ function isFileEditArgs(args) {
4349
+ return "file_path" in args && "old_text" in args && "new_text" in args;
4350
+ }
4351
+ function isFileNewArgs(args) {
4352
+ return "file_path" in args && "content" in args;
4353
+ }
4354
+ function isTodoWriteArgs(args) {
4355
+ return "todos" in args && Array.isArray(args.todos);
4356
+ }
4357
+ function isAssignTaskArgs(args) {
4358
+ return "agent" in args && "task" in args && "description" in args;
4359
+ }
4360
+ function isWebSearchArgs(args) {
4361
+ return "query" in args && typeof args.query === "string";
4362
+ }
4363
+ function isWebFetchArgs(args) {
4364
+ return "url" in args && typeof args.url === "string";
4365
+ }
4366
+ function isDirLsArgs(args) {
4367
+ return !("cmd" in args) && !("url" in args) && !("query" in args) && !("todos" in args) && !("agent" in args) && !("file_path" in args) && !("content" in args);
4368
+ }
4369
+
4370
+ // src/tools/type-guards.ts
4371
+ function isSuccess(result) {
4372
+ return result.status === "success";
4373
+ }
4374
+ function isError(result) {
4375
+ return result.status === "error";
4376
+ }
4377
+ function isTextResult(result) {
4378
+ return result.type === "text";
4379
+ }
4380
+ function isJsonResult(result) {
4381
+ return result.type === "json";
4382
+ }
4383
+ function isSuccessText(result) {
4384
+ return result.status === "success" && result.type === "text";
4385
+ }
4386
+ function isSuccessJson(result) {
4387
+ return result.status === "success" && result.type === "json";
4388
+ }
4389
+
4390
+ // src/tools/tool-type-guards.ts
4391
+ function isBashResult(result) {
4392
+ return result.name === "bash_tool";
4393
+ }
4394
+ function isBashSuccess(result) {
4395
+ return result.name === "bash_tool" && result.status === "success" && result.type === "text";
4396
+ }
4397
+ function isFileReadResult(result) {
4398
+ return result.name === "file_read";
4399
+ }
4400
+ function isFileReadSuccess(result) {
4401
+ return result.name === "file_read" && result.status === "success" && result.type === "text";
4402
+ }
4403
+ function isFileEditResult(result) {
4404
+ return result.name === "file_edit";
4405
+ }
4406
+ function isFileEditSuccess(result) {
4407
+ return result.name === "file_edit" && result.status === "success" && result.type === "text";
4408
+ }
4409
+ function isFileNewResult(result) {
4410
+ return result.name === "file_new";
4411
+ }
4412
+ function isFileNewSuccess(result) {
4413
+ return result.name === "file_new" && result.status === "success" && result.type === "text";
4414
+ }
4415
+ function isDirLsResult(result) {
4416
+ return result.name === "dir_ls";
4417
+ }
4418
+ function isDirLsSuccess(result) {
4419
+ return result.name === "dir_ls" && result.status === "success" && result.type === "json";
4420
+ }
4421
+ function isWebSearchResult(result) {
4422
+ return result.name === "web_search";
4423
+ }
4424
+ function isWebSearchSuccess(result) {
4425
+ return result.name === "web_search" && result.status === "success" && result.type === "json";
4426
+ }
4427
+ function isWebFetchResult(result) {
4428
+ return result.name === "web_fetch";
4429
+ }
4430
+ function isWebFetchSuccess(result) {
4431
+ return result.name === "web_fetch" && result.status === "success" && result.type === "text";
4432
+ }
4433
+ function isTodoWriteResult(result) {
4434
+ return result.name === "todo_write";
4435
+ }
4436
+ function isTodoWriteSuccess(result) {
4437
+ return result.name === "todo_write" && result.status === "success" && result.type === "text";
4438
+ }
4439
+ function isAssignResult(result) {
4440
+ return result.name === "assign_task";
4441
+ }
4442
+ function isAssignSuccess(result) {
4443
+ return result.name === "assign_task" && result.status === "success" && result.type === "text";
4444
+ }
4445
+
3797
4446
  // src/agent-file-persistence.ts
3798
4447
  import * as fs8 from "fs";
3799
4448
  import * as path7 from "path";
@@ -3918,6 +4567,16 @@ var AgentFilePersistence = class {
3918
4567
  }
3919
4568
  };
3920
4569
 
4570
+ // src/sub-agent-types.ts
4571
+ function parseSubAgentToolCallArguments(argsString) {
4572
+ if (!argsString) return {};
4573
+ try {
4574
+ return JSON.parse(argsString);
4575
+ } catch {
4576
+ return {};
4577
+ }
4578
+ }
4579
+
3921
4580
  // src/llm-providers/llm-utils.ts
3922
4581
  function mergeChoices(choices) {
3923
4582
  const contentParts = [];
@@ -5722,7 +6381,11 @@ var MCPToolPort = class {
5722
6381
  try {
5723
6382
  const res = await this.client.callTool({ name: original, arguments: c.parameters || {} });
5724
6383
  const flat = flattenMcpContent(res.content);
5725
- return { id: c.id, name: c.name, status: "success", type: flat.type, result: flat.value };
6384
+ if (flat.type === "text") {
6385
+ return { id: c.id, name: c.name, status: "success", type: "text", result: flat.value };
6386
+ } else {
6387
+ return { id: c.id, name: c.name, status: "success", type: "json", result: flat.value };
6388
+ }
5726
6389
  } catch (err2) {
5727
6390
  const message = err2 instanceof Error ? err2.message : String(err2);
5728
6391
  return { id: c.id, name: c.name, status: "error", type: "text", result: message };
@@ -5841,19 +6504,61 @@ export {
5841
6504
  buildAgentCreationPrompt,
5842
6505
  buildInjectedSystem,
5843
6506
  canonicalizeTerminalPaste,
6507
+ convertToolCall,
6508
+ convertToolCalls,
5844
6509
  createEmptySnapshot,
5845
6510
  createLLM,
6511
+ err,
5846
6512
  generateFolderTree,
5847
6513
  getAvailableProviders,
5848
6514
  getFallbackLimits,
5849
6515
  getProviderLabel,
6516
+ isAssignResult,
6517
+ isAssignSuccess,
6518
+ isAssignTaskArgs,
6519
+ isBashResult,
6520
+ isBashSuccess,
6521
+ isBashToolArgs,
6522
+ isDirLsArgs,
6523
+ isDirLsResult,
6524
+ isDirLsSuccess,
6525
+ isError,
6526
+ isFileEditArgs,
6527
+ isFileEditResult,
6528
+ isFileEditSuccess,
6529
+ isFileNewArgs,
6530
+ isFileNewResult,
6531
+ isFileNewSuccess,
6532
+ isFileReadArgs,
6533
+ isFileReadResult,
6534
+ isFileReadSuccess,
6535
+ isJsonResult,
6536
+ isSuccess,
6537
+ isSuccessJson,
6538
+ isSuccessText,
6539
+ isTextResult,
6540
+ isTodoWriteArgs,
6541
+ isTodoWriteResult,
6542
+ isTodoWriteSuccess,
6543
+ isWebFetchArgs,
6544
+ isWebFetchResult,
6545
+ isWebFetchSuccess,
6546
+ isWebSearchArgs,
6547
+ isWebSearchResult,
6548
+ isWebSearchSuccess,
5850
6549
  loadMCPConfig,
5851
6550
  normalizeModelInfo,
5852
6551
  normalizeModelLimits,
5853
6552
  normalizeNewlines,
6553
+ okJson,
6554
+ okText,
6555
+ parseJSON,
6556
+ parseSubAgentToolCallArguments,
6557
+ parseToolArguments,
5854
6558
  renderTemplate,
5855
6559
  resolveBackspaces,
5856
6560
  resolveCarriageReturns,
5857
6561
  stripAnsiAndControls,
5858
- supportsGetModels
6562
+ supportsGetModels,
6563
+ toolValidators
5859
6564
  };