@dexto/core 1.6.0 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/dist/agent/DextoAgent.cjs +25 -5
  2. package/dist/agent/DextoAgent.d.ts +12 -1
  3. package/dist/agent/DextoAgent.d.ts.map +1 -1
  4. package/dist/agent/DextoAgent.js +25 -5
  5. package/dist/agent/schemas.d.ts +18 -18
  6. package/dist/approval/manager.cjs +87 -27
  7. package/dist/approval/manager.d.ts +10 -1
  8. package/dist/approval/manager.d.ts.map +1 -1
  9. package/dist/approval/manager.js +87 -27
  10. package/dist/approval/schemas.cjs +22 -8
  11. package/dist/approval/schemas.d.ts +276 -102
  12. package/dist/approval/schemas.d.ts.map +1 -1
  13. package/dist/approval/schemas.js +22 -8
  14. package/dist/context/manager.cjs +2 -2
  15. package/dist/context/manager.d.ts +2 -1
  16. package/dist/context/manager.d.ts.map +1 -1
  17. package/dist/context/manager.js +2 -2
  18. package/dist/context/types.d.ts +3 -2
  19. package/dist/context/types.d.ts.map +1 -1
  20. package/dist/events/index.d.ts +17 -12
  21. package/dist/events/index.d.ts.map +1 -1
  22. package/dist/hooks/index.d.ts +1 -1
  23. package/dist/hooks/index.d.ts.map +1 -1
  24. package/dist/hooks/types.d.ts +1 -22
  25. package/dist/hooks/types.d.ts.map +1 -1
  26. package/dist/llm/executor/stream-processor.cjs +3 -3
  27. package/dist/llm/executor/stream-processor.d.ts +3 -2
  28. package/dist/llm/executor/stream-processor.d.ts.map +1 -1
  29. package/dist/llm/executor/stream-processor.js +3 -3
  30. package/dist/llm/executor/turn-executor.cjs +3 -3
  31. package/dist/llm/executor/turn-executor.d.ts +1 -1
  32. package/dist/llm/executor/turn-executor.d.ts.map +1 -1
  33. package/dist/llm/executor/turn-executor.js +3 -3
  34. package/dist/llm/providers/local/schemas.d.ts +2 -2
  35. package/dist/llm/schemas.d.ts +4 -4
  36. package/dist/llm/services/vercel.cjs +1 -1
  37. package/dist/llm/services/vercel.js +1 -1
  38. package/dist/logger/default-logger-factory.d.ts +12 -12
  39. package/dist/logger/v2/dexto-logger.cjs +35 -0
  40. package/dist/logger/v2/dexto-logger.d.ts +19 -0
  41. package/dist/logger/v2/dexto-logger.d.ts.map +1 -1
  42. package/dist/logger/v2/dexto-logger.js +35 -0
  43. package/dist/logger/v2/schemas.d.ts +6 -6
  44. package/dist/logger/v2/test-utils.cjs +2 -0
  45. package/dist/logger/v2/test-utils.d.ts.map +1 -1
  46. package/dist/logger/v2/test-utils.js +2 -0
  47. package/dist/logger/v2/types.d.ts +14 -1
  48. package/dist/logger/v2/types.d.ts.map +1 -1
  49. package/dist/mcp/schemas.d.ts +15 -15
  50. package/dist/memory/schemas.d.ts +4 -4
  51. package/dist/prompts/schemas.d.ts +7 -7
  52. package/dist/systemPrompt/in-built-prompts.cjs +5 -5
  53. package/dist/systemPrompt/in-built-prompts.d.ts +1 -1
  54. package/dist/systemPrompt/in-built-prompts.d.ts.map +1 -1
  55. package/dist/systemPrompt/in-built-prompts.js +5 -5
  56. package/dist/systemPrompt/schemas.d.ts +5 -5
  57. package/dist/systemPrompt/types.d.ts +11 -0
  58. package/dist/systemPrompt/types.d.ts.map +1 -1
  59. package/dist/tools/display-types.d.ts +10 -0
  60. package/dist/tools/display-types.d.ts.map +1 -1
  61. package/dist/tools/index.cjs +3 -1
  62. package/dist/tools/index.d.ts +1 -0
  63. package/dist/tools/index.d.ts.map +1 -1
  64. package/dist/tools/index.js +1 -0
  65. package/dist/tools/presentation.cjs +49 -0
  66. package/dist/tools/presentation.d.ts +11 -0
  67. package/dist/tools/presentation.d.ts.map +1 -0
  68. package/dist/tools/presentation.js +24 -0
  69. package/dist/tools/tool-manager.cjs +322 -155
  70. package/dist/tools/tool-manager.d.ts +23 -25
  71. package/dist/tools/tool-manager.d.ts.map +1 -1
  72. package/dist/tools/tool-manager.js +322 -155
  73. package/dist/tools/types.d.ts +134 -55
  74. package/dist/tools/types.d.ts.map +1 -1
  75. package/dist/utils/path.cjs +10 -1
  76. package/dist/utils/path.d.ts +5 -2
  77. package/dist/utils/path.d.ts.map +1 -1
  78. package/dist/utils/path.js +10 -1
  79. package/package.json +2 -2
@@ -26,6 +26,7 @@ let _ToolManager = class _ToolManager {
26
26
  agentEventBus;
27
27
  toolPolicies;
28
28
  toolExecutionContextFactory;
29
+ contributorContextFactory;
29
30
  // Hook support - set after construction to avoid circular dependencies
30
31
  hookManager;
31
32
  sessionManager;
@@ -384,16 +385,175 @@ let _ToolManager = class _ToolManager {
384
385
  });
385
386
  }
386
387
  // ==================== Pattern Approval Helpers ====================
388
+ getToolApprovalPatternKeyFn(toolName) {
389
+ const tool = this.agentTools.get(toolName);
390
+ return tool?.approval?.patternKey;
391
+ }
392
+ getToolSuggestApprovalPatternsFn(toolName) {
393
+ const tool = this.agentTools.get(toolName);
394
+ return tool?.approval?.suggestPatterns;
395
+ }
396
+ getToolApprovalOverrideFn(toolName) {
397
+ const tool = this.agentTools.get(toolName);
398
+ return tool?.approval?.override;
399
+ }
400
+ getToolApprovalOnGrantedFn(toolName) {
401
+ const tool = this.agentTools.get(toolName);
402
+ return tool?.approval?.onGranted;
403
+ }
404
+ getToolPreviewFn(toolName) {
405
+ const tool = this.agentTools.get(toolName);
406
+ return tool?.presentation?.preview;
407
+ }
408
+ getToolDescribeHeaderFn(toolName) {
409
+ const tool = this.agentTools.get(toolName);
410
+ return tool?.presentation?.describeHeader;
411
+ }
412
+ getToolDescribeArgsFn(toolName) {
413
+ const tool = this.agentTools.get(toolName);
414
+ return tool?.presentation?.describeArgs;
415
+ }
416
+ getToolDescribeResultFn(toolName) {
417
+ const tool = this.agentTools.get(toolName);
418
+ return tool?.presentation?.describeResult;
419
+ }
420
+ buildGenericToolPresentationSnapshot(toolName) {
421
+ const toTitleCase = (name) => name.replace(/[_-]+/g, " ").split(" ").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
422
+ const isMcp = toolName.startsWith(_ToolManager.MCP_TOOL_PREFIX);
423
+ const fallbackTitle = (() => {
424
+ if (!isMcp) {
425
+ return toTitleCase(toolName);
426
+ }
427
+ const actualToolName = toolName.substring(_ToolManager.MCP_TOOL_PREFIX.length);
428
+ const parts = actualToolName.split("--");
429
+ const toolPart = parts.length >= 2 ? parts.slice(1).join("--") : actualToolName;
430
+ return toTitleCase(toolPart);
431
+ })();
432
+ const snapshot = {
433
+ version: 1,
434
+ source: {
435
+ type: toolName.startsWith(_ToolManager.MCP_TOOL_PREFIX) ? "mcp" : "local"
436
+ },
437
+ header: {
438
+ title: fallbackTitle
439
+ }
440
+ };
441
+ if (snapshot.source?.type === "mcp") {
442
+ const actualToolName = toolName.substring(_ToolManager.MCP_TOOL_PREFIX.length);
443
+ const parts = actualToolName.split("--");
444
+ if (parts.length >= 2 && parts[0]) {
445
+ snapshot.source.mcpServerName = parts[0];
446
+ }
447
+ }
448
+ return snapshot;
449
+ }
450
+ getToolPresentationSnapshotForToolCallEvent(toolName, args, toolCallId, sessionId) {
451
+ const fallback = this.buildGenericToolPresentationSnapshot(toolName);
452
+ if (toolName.startsWith(_ToolManager.MCP_TOOL_PREFIX)) {
453
+ return fallback;
454
+ }
455
+ const describeHeader = this.getToolDescribeHeaderFn(toolName);
456
+ const describeArgs = this.getToolDescribeArgsFn(toolName);
457
+ if (!describeHeader && !describeArgs) {
458
+ return fallback;
459
+ }
460
+ try {
461
+ const validatedArgs = this.validateLocalToolArgs(toolName, args);
462
+ const context = this.buildToolExecutionContext({ sessionId, toolCallId });
463
+ const isPromiseLike = (value) => {
464
+ if (typeof value !== "object" || value === null) {
465
+ return false;
466
+ }
467
+ return typeof value.then === "function";
468
+ };
469
+ let nextSnapshot = fallback;
470
+ if (describeHeader) {
471
+ const header = describeHeader(validatedArgs, context);
472
+ if (!isPromiseLike(header) && header) {
473
+ nextSnapshot = {
474
+ ...nextSnapshot,
475
+ header: { ...nextSnapshot.header, ...header }
476
+ };
477
+ }
478
+ }
479
+ if (describeArgs) {
480
+ const argsPresentation = describeArgs(validatedArgs, context);
481
+ if (!isPromiseLike(argsPresentation) && argsPresentation) {
482
+ nextSnapshot = {
483
+ ...nextSnapshot,
484
+ args: argsPresentation
485
+ };
486
+ }
487
+ }
488
+ return nextSnapshot;
489
+ } catch (error) {
490
+ this.logger.debug(
491
+ `Tool presentation snapshot generation failed for '${toolName}': ${error instanceof Error ? error.message : String(error)}`
492
+ );
493
+ return fallback;
494
+ }
495
+ }
496
+ async getToolPresentationSnapshotForCall(toolName, args, toolCallId, sessionId) {
497
+ const fallback = this.buildGenericToolPresentationSnapshot(toolName);
498
+ if (toolName.startsWith(_ToolManager.MCP_TOOL_PREFIX)) {
499
+ return fallback;
500
+ }
501
+ const describeHeader = this.getToolDescribeHeaderFn(toolName);
502
+ const describeArgs = this.getToolDescribeArgsFn(toolName);
503
+ if (!describeHeader && !describeArgs) {
504
+ return fallback;
505
+ }
506
+ try {
507
+ const context = this.buildToolExecutionContext({ sessionId, toolCallId });
508
+ const describedHeader = describeHeader ? await Promise.resolve(describeHeader(args, context)) : null;
509
+ const describedArgs = describeArgs ? await Promise.resolve(describeArgs(args, context)) : null;
510
+ return {
511
+ ...fallback,
512
+ ...describedHeader ? { header: { ...fallback.header, ...describedHeader } } : {},
513
+ ...describedArgs ? { args: describedArgs } : {}
514
+ };
515
+ } catch (error) {
516
+ this.logger.debug(
517
+ `Tool presentation snapshot generation failed for '${toolName}': ${error instanceof Error ? error.message : String(error)}`
518
+ );
519
+ return fallback;
520
+ }
521
+ }
522
+ async augmentSnapshotWithResult(toolName, snapshot, result, args, toolCallId, sessionId) {
523
+ if (toolName.startsWith(_ToolManager.MCP_TOOL_PREFIX)) {
524
+ return snapshot;
525
+ }
526
+ const describeResult = this.getToolDescribeResultFn(toolName);
527
+ if (!describeResult) {
528
+ return snapshot;
529
+ }
530
+ try {
531
+ const context = this.buildToolExecutionContext({ sessionId, toolCallId });
532
+ const resultPresentation = await Promise.resolve(describeResult(result, args, context));
533
+ if (!resultPresentation) {
534
+ return snapshot;
535
+ }
536
+ return {
537
+ ...snapshot,
538
+ result: resultPresentation
539
+ };
540
+ } catch (error) {
541
+ this.logger.debug(
542
+ `Tool result presentation snapshot generation failed for '${toolName}': ${error instanceof Error ? error.message : String(error)}`
543
+ );
544
+ return snapshot;
545
+ }
546
+ }
387
547
  getToolPatternKey(toolName, args) {
388
548
  if (toolName.startsWith(_ToolManager.MCP_TOOL_PREFIX)) {
389
549
  return null;
390
550
  }
391
- const tool = this.agentTools.get(toolName);
392
- if (!tool?.getApprovalPatternKey) {
551
+ const getPatternKey = this.getToolApprovalPatternKeyFn(toolName);
552
+ if (!getPatternKey) {
393
553
  return null;
394
554
  }
395
555
  try {
396
- return tool.getApprovalPatternKey(args);
556
+ return getPatternKey(args);
397
557
  } catch (error) {
398
558
  this.logger.debug(
399
559
  `Pattern key generation failed for '${toolName}': ${error instanceof Error ? error.message : String(error)}`
@@ -405,12 +565,12 @@ let _ToolManager = class _ToolManager {
405
565
  if (toolName.startsWith(_ToolManager.MCP_TOOL_PREFIX)) {
406
566
  return void 0;
407
567
  }
408
- const tool = this.agentTools.get(toolName);
409
- if (!tool?.suggestApprovalPatterns) {
568
+ const suggestPatterns = this.getToolSuggestApprovalPatternsFn(toolName);
569
+ if (!suggestPatterns) {
410
570
  return void 0;
411
571
  }
412
572
  try {
413
- const patterns = tool.suggestApprovalPatterns(args);
573
+ const patterns = suggestPatterns(args);
414
574
  return patterns.length > 0 ? patterns : void 0;
415
575
  } catch (error) {
416
576
  this.logger.debug(
@@ -436,8 +596,7 @@ let _ToolManager = class _ToolManager {
436
596
  if (request.sessionId !== sessionId) {
437
597
  return false;
438
598
  }
439
- const metadata = request.metadata;
440
- return metadata.toolName === toolName;
599
+ return request.metadata.toolName === toolName;
441
600
  },
442
601
  { rememberChoice: false }
443
602
  // Don't propagate remember choice to auto-approved requests
@@ -456,8 +615,7 @@ let _ToolManager = class _ToolManager {
456
615
  if (toolName.startsWith(_ToolManager.MCP_TOOL_PREFIX)) {
457
616
  return;
458
617
  }
459
- const tool = this.agentTools.get(toolName);
460
- const getPatternKey = tool?.getApprovalPatternKey;
618
+ const getPatternKey = this.getToolApprovalPatternKeyFn(toolName);
461
619
  if (!getPatternKey) {
462
620
  return;
463
621
  }
@@ -469,17 +627,17 @@ let _ToolManager = class _ToolManager {
469
627
  if (request.sessionId !== sessionId) {
470
628
  return false;
471
629
  }
472
- const metadata = request.metadata;
473
- if (metadata.toolName !== toolName) {
630
+ if (request.metadata.toolName !== toolName) {
474
631
  return false;
475
632
  }
476
- const args = metadata.args;
633
+ const args = request.metadata.args;
477
634
  if (typeof args !== "object" || args === null) {
478
635
  return false;
479
636
  }
637
+ const argsRecord = args;
480
638
  let patternKey;
481
639
  try {
482
- patternKey = getPatternKey(args);
640
+ patternKey = getPatternKey(argsRecord);
483
641
  } catch (error) {
484
642
  this.logger.debug(
485
643
  `Pattern key generation failed for '${toolName}': ${error instanceof Error ? error.message : String(error)}`
@@ -498,43 +656,38 @@ let _ToolManager = class _ToolManager {
498
656
  );
499
657
  }
500
658
  }
501
- /**
502
- * Auto-approve pending directory access requests that match a newly approved directory access request.
503
- *
504
- * This handles the case where parallel file operations request directory approval concurrently.
505
- */
506
- autoApprovePendingDirectoryAccessRequests(options) {
507
- const count = this.approvalManager.autoApprovePendingRequests(
508
- (request) => {
509
- if (request.type !== ApprovalType.DIRECTORY_ACCESS) {
510
- return false;
511
- }
512
- if (request.sessionId !== options.sessionId) {
513
- return false;
514
- }
515
- const metadata = request.metadata;
516
- if (typeof metadata?.parentDir !== "string" || metadata.parentDir !== options.parentDir) {
517
- return false;
518
- }
519
- if (options.rememberDirectory) {
520
- return true;
521
- }
522
- if (typeof metadata?.operation !== "string" || typeof metadata?.toolName !== "string") {
523
- return false;
524
- }
525
- return metadata.operation === options.operation && metadata.toolName === options.toolName;
526
- },
527
- { rememberDirectory: false }
528
- );
529
- if (count > 0) {
530
- this.logger.info(
531
- `Auto-approved ${count} parallel request(s) for directory '${options.parentDir}' after directory access was approved`
532
- );
533
- }
534
- }
535
659
  getMcpManager() {
536
660
  return this.mcpManager;
537
661
  }
662
+ setContributorContextFactory(factory) {
663
+ this.contributorContextFactory = factory ?? void 0;
664
+ }
665
+ async buildContributorContext() {
666
+ const baseWorkspace = this.currentWorkspace ?? null;
667
+ const baseContext = {
668
+ mcpManager: this.mcpManager,
669
+ workspace: baseWorkspace
670
+ };
671
+ if (!this.contributorContextFactory) {
672
+ return baseContext;
673
+ }
674
+ try {
675
+ const overrides = await this.contributorContextFactory() ?? {};
676
+ const workspace = overrides.workspace !== void 0 ? overrides.workspace : baseWorkspace;
677
+ const environment = overrides.environment !== void 0 ? overrides.environment : baseContext.environment;
678
+ const mcpManager = overrides.mcpManager ?? baseContext.mcpManager;
679
+ return {
680
+ mcpManager,
681
+ workspace,
682
+ ...environment !== void 0 ? { environment } : {}
683
+ };
684
+ } catch (error) {
685
+ this.logger.warn(
686
+ `Failed to build contributor context: ${error instanceof Error ? error.message : String(error)}`
687
+ );
688
+ return baseContext;
689
+ }
690
+ }
538
691
  /**
539
692
  * Get all MCP tools (delegates to mcpManager.getAllTools())
540
693
  * This provides access to MCP tools while maintaining separation of concerns
@@ -554,7 +707,7 @@ let _ToolManager = class _ToolManager {
554
707
  };
555
708
  return this.toolExecutionContextFactory(baseContext);
556
709
  }
557
- validateLocalToolArgsOrThrow(toolName, args) {
710
+ validateLocalToolArgs(toolName, args) {
558
711
  if (toolName.startsWith(_ToolManager.MCP_TOOL_PREFIX)) {
559
712
  return args;
560
713
  }
@@ -675,15 +828,22 @@ let _ToolManager = class _ToolManager {
675
828
  async executeTool(toolName, args, toolCallId, sessionId, abortSignal) {
676
829
  const { toolArgs: rawToolArgs, meta } = extractToolCallMeta(args);
677
830
  let toolArgs = rawToolArgs;
831
+ const callDescription = typeof meta.callDescription === "string" ? meta.callDescription : typeof rawToolArgs.description === "string" ? rawToolArgs.description : void 0;
678
832
  const backgroundTasksEnabled = isBackgroundTasksEnabled();
679
- const toolDisplayName = this.agentTools.get(toolName)?.displayName;
680
833
  this.logger.debug(`\u{1F527} Tool execution requested: '${toolName}' (toolCallId: ${toolCallId})`);
681
834
  this.logger.debug(`Tool args: ${JSON.stringify(toolArgs, null, 2)}`);
682
835
  if (sessionId) {
836
+ const presentationSnapshot = this.getToolPresentationSnapshotForToolCallEvent(
837
+ toolName,
838
+ toolArgs,
839
+ toolCallId,
840
+ sessionId
841
+ );
683
842
  this.agentEventBus.emit("llm:tool-call", {
684
843
  toolName,
685
- ...toolDisplayName !== void 0 && { toolDisplayName },
844
+ presentationSnapshot,
686
845
  args: toolArgs,
846
+ ...callDescription !== void 0 && { callDescription },
687
847
  callId: toolCallId,
688
848
  sessionId
689
849
  });
@@ -691,13 +851,14 @@ let _ToolManager = class _ToolManager {
691
851
  const {
692
852
  requireApproval,
693
853
  approvalStatus,
694
- args: validatedToolArgs
854
+ args: validatedToolArgs,
855
+ presentationSnapshot: callSnapshot
695
856
  } = await this.handleToolApproval(
696
857
  toolName,
697
858
  toolArgs,
698
859
  toolCallId,
699
860
  sessionId,
700
- meta.callDescription
861
+ callDescription
701
862
  );
702
863
  toolArgs = validatedToolArgs;
703
864
  this.logger.debug(`\u2705 Tool execution approved: ${toolName}`);
@@ -731,7 +892,7 @@ let _ToolManager = class _ToolManager {
731
892
  );
732
893
  toolArgs = modifiedPayload.args;
733
894
  try {
734
- toolArgs = this.validateLocalToolArgsOrThrow(toolName, toolArgs);
895
+ toolArgs = this.validateLocalToolArgs(toolName, toolArgs);
735
896
  } catch (error) {
736
897
  this.logger.error(
737
898
  `Post-hook validation failed for tool '${toolName}': a beforeToolCall hook may have set invalid args`
@@ -854,9 +1015,17 @@ let _ToolManager = class _ToolManager {
854
1015
  );
855
1016
  result = modifiedPayload.result;
856
1017
  }
1018
+ const presentationSnapshot = await this.augmentSnapshotWithResult(
1019
+ toolName,
1020
+ callSnapshot,
1021
+ result,
1022
+ toolArgs,
1023
+ toolCallId,
1024
+ sessionId
1025
+ );
857
1026
  return {
858
1027
  result,
859
- ...toolDisplayName !== void 0 && { toolDisplayName },
1028
+ ...presentationSnapshot !== void 0 && { presentationSnapshot },
860
1029
  ...requireApproval && { requireApproval, approvalStatus }
861
1030
  };
862
1031
  } catch (error) {
@@ -981,124 +1150,113 @@ let _ToolManager = class _ToolManager {
981
1150
  );
982
1151
  }
983
1152
  /**
984
- * Check if a tool has a custom approval override and handle it.
985
- * Tools can implement getApprovalOverride() to request specialized approval flows
986
- * (e.g., directory access approval for file tools) instead of default tool confirmation.
987
- *
988
- * @param toolName Tool name
989
- * @param args The tool arguments
990
- * @param sessionId Optional session ID
991
- * @returns { handled: true } if custom approval was processed, { handled: false } to continue normal flow
1153
+ * Handle tool approval flow. Checks various precedence levels to determine
1154
+ * if a tool should be auto-approved, denied, or requires manual approval.
992
1155
  */
993
- async checkCustomApprovalOverride(toolName, args, toolCallId, sessionId) {
994
- if (toolName.startsWith(_ToolManager.MCP_TOOL_PREFIX)) {
995
- return { handled: false };
996
- }
997
- const tool = this.agentTools.get(toolName);
998
- if (!tool?.getApprovalOverride) {
999
- return { handled: false };
1000
- }
1001
- const context = this.buildToolExecutionContext({ sessionId, toolCallId });
1002
- const approvalRequest = await tool.getApprovalOverride(args, context);
1003
- if (!approvalRequest) {
1004
- return { handled: false };
1005
- }
1006
- this.logger.debug(
1007
- `Tool '${toolName}' requested custom approval: type=${approvalRequest.type}`
1008
- );
1009
- if (sessionId && !approvalRequest.sessionId) {
1010
- approvalRequest.sessionId = sessionId;
1011
- }
1012
- const response = await this.approvalManager.requestApproval(approvalRequest);
1013
- if (response.status === ApprovalStatus.APPROVED) {
1014
- if (tool.onApprovalGranted) {
1015
- tool.onApprovalGranted(response, context, approvalRequest);
1016
- }
1017
- if (approvalRequest.type === ApprovalType.DIRECTORY_ACCESS) {
1018
- const metadata = approvalRequest.metadata;
1019
- const parentDir = typeof metadata?.parentDir === "string" ? metadata.parentDir : null;
1020
- const operation = typeof metadata?.operation === "string" ? metadata.operation : null;
1021
- if (parentDir && operation) {
1022
- const data = response.data;
1023
- const rememberDirectory = data?.rememberDirectory ?? false;
1024
- this.autoApprovePendingDirectoryAccessRequests({
1025
- parentDir,
1026
- operation,
1027
- sessionId,
1028
- toolName,
1029
- rememberDirectory
1030
- });
1031
- }
1032
- }
1156
+ async handleToolApproval(toolName, args, toolCallId, sessionId, callDescription) {
1157
+ if (this.isInAlwaysDenyList(toolName)) {
1033
1158
  this.logger.info(
1034
- `Custom approval granted for '${toolName}', type=${approvalRequest.type}, session=${sessionId ?? "global"}`
1159
+ `Tool '${toolName}' is in static deny list \u2013 blocking execution (session: ${sessionId ?? "global"})`
1035
1160
  );
1036
- return { handled: true };
1161
+ throw ToolError.executionDenied(toolName, sessionId);
1037
1162
  }
1038
- this.logger.info(
1039
- `Custom approval denied for '${toolName}', type=${approvalRequest.type}, reason=${response.reason ?? "unknown"}`
1163
+ const validatedArgs = this.validateLocalToolArgs(toolName, args);
1164
+ const presentationSnapshot = await this.getToolPresentationSnapshotForCall(
1165
+ toolName,
1166
+ validatedArgs,
1167
+ toolCallId,
1168
+ sessionId
1040
1169
  );
1041
- if (approvalRequest.type === "directory_access") {
1042
- const metadata = approvalRequest.metadata;
1043
- throw ToolError.directoryAccessDenied(
1044
- metadata?.parentDir ?? "unknown directory",
1045
- sessionId
1046
- );
1170
+ let directoryAccess;
1171
+ let directoryAccessApprovalRequest;
1172
+ if (!toolName.startsWith(_ToolManager.MCP_TOOL_PREFIX)) {
1173
+ const getApprovalOverride = this.getToolApprovalOverrideFn(toolName);
1174
+ if (getApprovalOverride) {
1175
+ const context = this.buildToolExecutionContext({ sessionId, toolCallId });
1176
+ const approvalRequest = await getApprovalOverride(validatedArgs, context);
1177
+ if (approvalRequest) {
1178
+ if (approvalRequest.type === ApprovalType.DIRECTORY_ACCESS) {
1179
+ const metadata = approvalRequest.metadata;
1180
+ if (typeof metadata !== "object" || metadata === null || typeof metadata.path !== "string" || typeof metadata.parentDir !== "string" || typeof metadata.operation !== "string" || typeof metadata.toolName !== "string") {
1181
+ throw ToolError.configInvalid(
1182
+ `Tool '${toolName}' returned invalid directory access metadata`
1183
+ );
1184
+ }
1185
+ directoryAccess = metadata;
1186
+ directoryAccessApprovalRequest = approvalRequest;
1187
+ } else {
1188
+ this.logger.debug(
1189
+ `Tool '${toolName}' requested custom approval: type=${approvalRequest.type}`
1190
+ );
1191
+ if (sessionId && !approvalRequest.sessionId) {
1192
+ approvalRequest.sessionId = sessionId;
1193
+ }
1194
+ const response = await this.approvalManager.requestApproval(approvalRequest);
1195
+ if (response.status === ApprovalStatus.APPROVED) {
1196
+ const onGranted = this.getToolApprovalOnGrantedFn(toolName);
1197
+ if (onGranted) {
1198
+ await Promise.resolve(
1199
+ onGranted(response, context, approvalRequest)
1200
+ );
1201
+ }
1202
+ this.logger.info(
1203
+ `Custom approval granted for '${toolName}', type=${approvalRequest.type}, session=${sessionId ?? "global"}`
1204
+ );
1205
+ return {
1206
+ requireApproval: true,
1207
+ approvalStatus: "approved",
1208
+ args: validatedArgs,
1209
+ presentationSnapshot
1210
+ };
1211
+ }
1212
+ this.logger.info(
1213
+ `Custom approval denied for '${toolName}', type=${approvalRequest.type}, reason=${response.reason ?? "unknown"}`
1214
+ );
1215
+ throw ToolError.executionDenied(toolName, sessionId);
1216
+ }
1217
+ }
1218
+ }
1047
1219
  }
1048
- throw ToolError.executionDenied(toolName, sessionId);
1049
- }
1050
- /**
1051
- * Handle tool approval flow. Checks various precedence levels to determine
1052
- * if a tool should be auto-approved, denied, or requires manual approval.
1053
- */
1054
- async handleToolApproval(toolName, args, toolCallId, sessionId, callDescription) {
1055
- const validatedArgs = this.validateLocalToolArgsOrThrow(toolName, args);
1056
1220
  const quickResult = await this.tryQuickApprovalResolution(
1057
1221
  toolName,
1058
1222
  validatedArgs,
1059
- toolCallId,
1060
- sessionId
1223
+ sessionId,
1224
+ directoryAccess
1061
1225
  );
1062
1226
  if (quickResult !== null) {
1063
- return { ...quickResult, args: validatedArgs };
1227
+ return { ...quickResult, args: validatedArgs, presentationSnapshot };
1064
1228
  }
1065
1229
  const manualResult = await this.requestManualApproval(
1066
1230
  toolName,
1067
1231
  validatedArgs,
1068
1232
  toolCallId,
1069
1233
  sessionId,
1070
- callDescription
1234
+ directoryAccess,
1235
+ directoryAccessApprovalRequest,
1236
+ callDescription,
1237
+ presentationSnapshot
1071
1238
  );
1072
- return { ...manualResult, args: validatedArgs };
1239
+ return { ...manualResult, args: validatedArgs, presentationSnapshot };
1073
1240
  }
1074
1241
  /**
1075
1242
  * Try to resolve tool approval quickly based on policies and cached permissions.
1076
1243
  * Returns null if manual approval is needed.
1077
1244
  *
1078
1245
  * Precedence order (highest to lowest):
1079
- * 1. Static deny list (security - always blocks)
1080
- * 2. Custom approval override (tool-specific approval flows)
1081
- * 3. Session auto-approve (skill allowed-tools)
1082
- * 4. Static allow list
1083
- * 5. Dynamic "remembered" allowed list
1084
- * 6. Tool approval patterns
1085
- * 7. Approval mode (auto-approve/auto-deny)
1246
+ * 1. Directory access requirement (outside-root paths)
1247
+ * 2. Session auto-approve (skill allowed-tools)
1248
+ * 3. Static allow list
1249
+ * 4. Dynamic "remembered" allowed list
1250
+ * 5. Tool approval patterns
1251
+ * 6. Approval mode (auto-approve/auto-deny)
1086
1252
  */
1087
- async tryQuickApprovalResolution(toolName, args, toolCallId, sessionId) {
1088
- if (this.isInAlwaysDenyList(toolName)) {
1089
- this.logger.info(
1090
- `Tool '${toolName}' is in static deny list \u2013 blocking execution (session: ${sessionId ?? "global"})`
1091
- );
1092
- throw ToolError.executionDenied(toolName, sessionId);
1093
- }
1094
- const customApprovalResult = await this.checkCustomApprovalOverride(
1095
- toolName,
1096
- args,
1097
- toolCallId,
1098
- sessionId
1099
- );
1100
- if (customApprovalResult.handled) {
1101
- return { requireApproval: true, approvalStatus: "approved" };
1253
+ async tryQuickApprovalResolution(toolName, args, sessionId, directoryAccess) {
1254
+ if (directoryAccess) {
1255
+ if (this.approvalMode === "auto-approve") {
1256
+ this.approvalManager.addApprovedDirectory(directoryAccess.parentDir, "once");
1257
+ return { requireApproval: false };
1258
+ }
1259
+ return null;
1102
1260
  }
1103
1261
  if (sessionId && this.isToolAutoApprovedForSession(sessionId, toolName)) {
1104
1262
  this.logger.info(
@@ -1139,12 +1297,11 @@ let _ToolManager = class _ToolManager {
1139
1297
  * Request manual approval from the user for a tool execution.
1140
1298
  * Generates preview, sends approval request, and handles the response.
1141
1299
  */
1142
- async requestManualApproval(toolName, args, toolCallId, sessionId, callDescription) {
1300
+ async requestManualApproval(toolName, args, toolCallId, sessionId, directoryAccess, directoryAccessApprovalRequest, callDescription, presentationSnapshot) {
1143
1301
  this.logger.info(
1144
1302
  `Tool approval requested for ${toolName}, sessionId: ${sessionId ?? "global"}`
1145
1303
  );
1146
1304
  try {
1147
- const toolDisplayName = this.agentTools.get(toolName)?.displayName;
1148
1305
  const displayPreview = await this.generateToolPreview(
1149
1306
  toolName,
1150
1307
  args,
@@ -1154,14 +1311,24 @@ let _ToolManager = class _ToolManager {
1154
1311
  const suggestedPatterns = this.getToolSuggestedPatterns(toolName, args);
1155
1312
  const response = await this.approvalManager.requestToolApproval({
1156
1313
  toolName,
1157
- ...toolDisplayName !== void 0 && { toolDisplayName },
1314
+ ...presentationSnapshot !== void 0 && { presentationSnapshot },
1158
1315
  toolCallId,
1159
1316
  args,
1160
1317
  ...callDescription !== void 0 && { description: callDescription },
1161
1318
  ...sessionId !== void 0 && { sessionId },
1162
1319
  ...displayPreview !== void 0 && { displayPreview },
1320
+ ...directoryAccess !== void 0 && { directoryAccess },
1163
1321
  ...suggestedPatterns !== void 0 && { suggestedPatterns }
1164
1322
  });
1323
+ if (response.status === ApprovalStatus.APPROVED && directoryAccessApprovalRequest !== void 0) {
1324
+ const onGranted = this.getToolApprovalOnGrantedFn(toolName);
1325
+ if (onGranted) {
1326
+ const context = this.buildToolExecutionContext({ sessionId, toolCallId });
1327
+ await Promise.resolve(
1328
+ onGranted(response, context, directoryAccessApprovalRequest)
1329
+ );
1330
+ }
1331
+ }
1165
1332
  if (response.status === ApprovalStatus.APPROVED && response.data) {
1166
1333
  await this.handleRememberChoice(toolName, response, sessionId);
1167
1334
  }
@@ -1183,13 +1350,13 @@ let _ToolManager = class _ToolManager {
1183
1350
  * Generate a preview for the tool approval UI if the tool supports it.
1184
1351
  */
1185
1352
  async generateToolPreview(toolName, args, toolCallId, sessionId) {
1186
- const tool = this.agentTools.get(toolName);
1187
- if (!tool?.generatePreview) {
1353
+ const previewFn = this.getToolPreviewFn(toolName);
1354
+ if (!previewFn) {
1188
1355
  return void 0;
1189
1356
  }
1190
1357
  try {
1191
1358
  const context = this.buildToolExecutionContext({ sessionId, toolCallId });
1192
- const preview = await tool.generatePreview(args, context);
1359
+ const preview = await previewFn(args, context);
1193
1360
  this.logger.debug(`Generated preview for ${toolName}`);
1194
1361
  return preview ?? void 0;
1195
1362
  } catch (previewError) {
@@ -1218,7 +1385,7 @@ let _ToolManager = class _ToolManager {
1218
1385
  `Tool '${toolName}' added to allowed tools for session '${allowSessionId ?? "global"}' (remember choice selected)`
1219
1386
  );
1220
1387
  this.autoApprovePendingToolRequests(toolName, allowSessionId);
1221
- } else if (rememberPattern && this.agentTools.get(toolName)?.getApprovalPatternKey) {
1388
+ } else if (rememberPattern && this.getToolApprovalPatternKeyFn(toolName)) {
1222
1389
  this.approvalManager.addPattern(toolName, rememberPattern);
1223
1390
  this.logger.info(`Pattern '${rememberPattern}' added for tool '${toolName}' approval`);
1224
1391
  this.autoApprovePendingPatternRequests(toolName, sessionId);