@within-7/minto 0.3.0 → 0.3.3

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 (80) hide show
  1. package/dist/components/SubagentProgress.js +10 -2
  2. package/dist/components/SubagentProgress.js.map +2 -2
  3. package/dist/constants/prompts.js +22 -1
  4. package/dist/constants/prompts.js.map +2 -2
  5. package/dist/entrypoints/cli.js +15 -9
  6. package/dist/entrypoints/cli.js.map +2 -2
  7. package/dist/permissions.js +121 -2
  8. package/dist/permissions.js.map +2 -2
  9. package/dist/screens/ResumeConversation.js +2 -0
  10. package/dist/screens/ResumeConversation.js.map +2 -2
  11. package/dist/services/taskStore.js +205 -0
  12. package/dist/services/taskStore.js.map +7 -0
  13. package/dist/tools/AskUserQuestionTool/AskUserQuestionTool.js +40 -3
  14. package/dist/tools/AskUserQuestionTool/AskUserQuestionTool.js.map +2 -2
  15. package/dist/tools/BashTool/BashTool.js +21 -4
  16. package/dist/tools/BashTool/BashTool.js.map +2 -2
  17. package/dist/tools/BashTool/prompt.js +6 -0
  18. package/dist/tools/BashTool/prompt.js.map +2 -2
  19. package/dist/tools/FileEditTool/FileEditTool.js +24 -9
  20. package/dist/tools/FileEditTool/FileEditTool.js.map +2 -2
  21. package/dist/tools/FileEditTool/prompt.js +4 -1
  22. package/dist/tools/FileEditTool/prompt.js.map +2 -2
  23. package/dist/tools/FileEditTool/utils.js +10 -4
  24. package/dist/tools/FileEditTool/utils.js.map +2 -2
  25. package/dist/tools/FileReadTool/FileReadTool.js +1 -1
  26. package/dist/tools/FileReadTool/FileReadTool.js.map +1 -1
  27. package/dist/tools/FileReadTool/prompt.js +16 -1
  28. package/dist/tools/FileReadTool/prompt.js.map +2 -2
  29. package/dist/tools/FileWriteTool/FileWriteTool.js +1 -1
  30. package/dist/tools/FileWriteTool/FileWriteTool.js.map +1 -1
  31. package/dist/tools/FileWriteTool/prompt.js +8 -1
  32. package/dist/tools/FileWriteTool/prompt.js.map +2 -2
  33. package/dist/tools/GlobTool/prompt.js +12 -1
  34. package/dist/tools/GlobTool/prompt.js.map +2 -2
  35. package/dist/tools/GrepTool/GrepTool.js +333 -65
  36. package/dist/tools/GrepTool/GrepTool.js.map +2 -2
  37. package/dist/tools/GrepTool/prompt.js +15 -8
  38. package/dist/tools/GrepTool/prompt.js.map +2 -2
  39. package/dist/tools/NotebookEditTool/NotebookEditTool.js +57 -45
  40. package/dist/tools/NotebookEditTool/NotebookEditTool.js.map +2 -2
  41. package/dist/tools/NotebookEditTool/prompt.js +1 -1
  42. package/dist/tools/NotebookEditTool/prompt.js.map +1 -1
  43. package/dist/tools/TaskCreateTool/TaskCreateTool.js +102 -0
  44. package/dist/tools/TaskCreateTool/TaskCreateTool.js.map +7 -0
  45. package/dist/tools/TaskCreateTool/prompt.js +47 -0
  46. package/dist/tools/TaskCreateTool/prompt.js.map +7 -0
  47. package/dist/tools/TaskGetTool/TaskGetTool.js +115 -0
  48. package/dist/tools/TaskGetTool/TaskGetTool.js.map +7 -0
  49. package/dist/tools/TaskGetTool/prompt.js +28 -0
  50. package/dist/tools/TaskGetTool/prompt.js.map +7 -0
  51. package/dist/tools/TaskListTool/TaskListTool.js +102 -0
  52. package/dist/tools/TaskListTool/TaskListTool.js.map +7 -0
  53. package/dist/tools/TaskListTool/prompt.js +27 -0
  54. package/dist/tools/TaskListTool/prompt.js.map +7 -0
  55. package/dist/tools/TaskStopTool/TaskStopTool.js +150 -0
  56. package/dist/tools/TaskStopTool/TaskStopTool.js.map +7 -0
  57. package/dist/tools/TaskStopTool/prompt.js +15 -0
  58. package/dist/tools/TaskStopTool/prompt.js.map +7 -0
  59. package/dist/tools/TaskTool/TaskTool.js +41 -1
  60. package/dist/tools/TaskTool/TaskTool.js.map +2 -2
  61. package/dist/tools/TaskUpdateTool/TaskUpdateTool.js +134 -0
  62. package/dist/tools/TaskUpdateTool/TaskUpdateTool.js.map +7 -0
  63. package/dist/tools/TaskUpdateTool/prompt.js +81 -0
  64. package/dist/tools/TaskUpdateTool/prompt.js.map +7 -0
  65. package/dist/tools/URLFetcherTool/prompt.js +1 -1
  66. package/dist/tools/URLFetcherTool/prompt.js.map +1 -1
  67. package/dist/tools.js +12 -0
  68. package/dist/tools.js.map +2 -2
  69. package/dist/utils/config.js.map +2 -2
  70. package/dist/utils/model.js +15 -2
  71. package/dist/utils/model.js.map +2 -2
  72. package/dist/utils/ripgrep.js +53 -1
  73. package/dist/utils/ripgrep.js.map +2 -2
  74. package/dist/utils/terminal.js +12 -0
  75. package/dist/utils/terminal.js.map +2 -2
  76. package/dist/utils/tooling/safeRender.js +13 -14
  77. package/dist/utils/tooling/safeRender.js.map +2 -2
  78. package/dist/version.js +2 -2
  79. package/dist/version.js.map +1 -1
  80. package/package.json +20 -28
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/permissions.ts"],
4
- "sourcesContent": ["import type { CanUseToolFn } from './hooks/useCanUseTool'\nimport { Tool, ToolUseContext } from './Tool'\nimport { BashTool, inputSchema } from './tools/BashTool/BashTool'\nimport { FileEditTool } from './tools/FileEditTool/FileEditTool'\nimport { FileWriteTool } from './tools/FileWriteTool/FileWriteTool'\nimport { NotebookEditTool } from './tools/NotebookEditTool/NotebookEditTool'\nimport { getCommandSubcommandPrefix, splitCommand } from './utils/commands'\nimport {\n getCurrentProjectConfig,\n saveCurrentProjectConfig,\n type SafetyMode,\n} from '@utils/config'\nimport { AbortError } from './utils/errors'\nimport { logError } from './utils/log'\nimport { grantWritePermissionForOriginalDir } from './utils/permissions/filesystem'\nimport { getCwd } from './utils/state'\nimport { PRODUCT_NAME } from './constants/product'\nimport {\n getToolRiskLevel,\n type RiskLevel,\n} from './utils/toolRiskClassification'\n\n// Commands that are known to be safe for execution\nconst SAFE_COMMANDS = new Set([\n 'git status',\n 'git diff',\n 'git log',\n 'git branch',\n 'pwd',\n 'tree',\n 'date',\n 'which',\n])\n\nexport const bashToolCommandHasExactMatchPermission = (\n tool: Tool,\n command: string,\n allowedTools: string[],\n): boolean => {\n if (SAFE_COMMANDS.has(command)) {\n return true\n }\n // Check exact match first\n if (allowedTools.includes(getPermissionKey(tool, { command }, null))) {\n return true\n }\n // Check if command is an exact match with an approved prefix\n if (allowedTools.includes(getPermissionKey(tool, { command }, command))) {\n return true\n }\n return false\n}\n\nexport const bashToolCommandHasPermission = (\n tool: Tool,\n command: string,\n prefix: string | null,\n allowedTools: string[],\n): boolean => {\n // Check exact match first\n if (bashToolCommandHasExactMatchPermission(tool, command, allowedTools)) {\n return true\n }\n return allowedTools.includes(getPermissionKey(tool, { command }, prefix))\n}\n\nexport const bashToolHasPermission = async (\n tool: Tool,\n command: string,\n context: ToolUseContext,\n allowedTools: string[],\n getCommandSubcommandPrefixFn = getCommandSubcommandPrefix,\n): Promise<PermissionResult> => {\n if (bashToolCommandHasExactMatchPermission(tool, command, allowedTools)) {\n // This is an exact match for a command that is allowed, so we can skip the prefix check\n return { result: true }\n }\n\n const subCommands = splitCommand(command).filter(_ => {\n // Denim likes to add this, we strip it out so we don't need to prompt the user each time\n if (_ === `cd ${getCwd()}`) {\n return false\n }\n return true\n })\n const commandSubcommandPrefix = await getCommandSubcommandPrefixFn(\n command,\n context.abortController.signal,\n )\n if (context.abortController.signal.aborted) {\n throw new AbortError()\n }\n\n if (commandSubcommandPrefix === null) {\n // Fail closed and ask for user approval if the command prefix query failed (e.g. due to network error)\n // This is NOT the same as `fullCommandPrefix.commandPrefix === null`, which means no prefix was detected\n return {\n result: false,\n message: `${PRODUCT_NAME} requested permissions to use ${tool.name}, but you haven't granted it yet.`,\n }\n }\n\n if (commandSubcommandPrefix.commandInjectionDetected) {\n // Only allow exact matches for potential command injections\n if (bashToolCommandHasExactMatchPermission(tool, command, allowedTools)) {\n return { result: true }\n } else {\n return {\n result: false,\n message: `${PRODUCT_NAME} requested permissions to use ${tool.name}, but you haven't granted it yet.`,\n }\n }\n }\n\n // After the commandInjectionDetected check above, TypeScript still sees the union type.\n // We know commandInjectionDetected is false here, so commandPrefix exists.\n // Use 'in' check for proper type narrowing\n const commandPrefix =\n 'commandPrefix' in commandSubcommandPrefix\n ? commandSubcommandPrefix.commandPrefix\n : null\n\n // If there is only one command, no need to process subCommands\n if (subCommands.length < 2) {\n if (\n bashToolCommandHasPermission(tool, command, commandPrefix, allowedTools)\n ) {\n return { result: true }\n } else {\n return {\n result: false,\n message: `${PRODUCT_NAME} requested permissions to use ${tool.name}, but you haven't granted it yet.`,\n }\n }\n }\n if (\n subCommands.every(subCommand => {\n const prefixResult =\n commandSubcommandPrefix.subcommandPrefixes.get(subCommand)\n if (prefixResult === undefined || prefixResult.commandInjectionDetected) {\n // If prefix result is missing or command injection is detected, always ask for permission\n return false\n }\n // After the check above, we know commandInjectionDetected is false\n // Use 'in' check for proper type narrowing\n const subCommandPrefix =\n 'commandPrefix' in prefixResult ? prefixResult.commandPrefix : null\n const hasPermission = bashToolCommandHasPermission(\n tool,\n subCommand,\n subCommandPrefix,\n allowedTools,\n )\n return hasPermission\n })\n ) {\n return { result: true }\n }\n return {\n result: false,\n message: `${PRODUCT_NAME} requested permissions to use ${tool.name}, but you haven't granted it yet.`,\n }\n}\n\ntype PermissionResult = { result: true } | { result: false; message: string }\n\n/**\n * Get effective safety mode from context options\n * Handles backward compatibility with legacy safeMode boolean\n */\nfunction getEffectiveSafetyMode(options: {\n safeMode?: boolean\n safetyMode?: SafetyMode\n}): SafetyMode {\n // New safetyMode takes precedence\n if (options.safetyMode) {\n return options.safetyMode\n }\n // Backward compatibility: convert legacy safeMode boolean\n // safeMode: true \u2192 'strict' (old behavior)\n // safeMode: false \u2192 'yolo' (permissive)\n if (options.safeMode === true) {\n return 'strict'\n }\n // Default to 'yolo' for non-technical users (zero interruption)\n return 'yolo'\n}\n\n/**\n * Check if a tool should be allowed based on safety mode and risk level\n *\n * Safety Mode Matrix:\n * | Mode | Safe Tools | Monitored Tools | Dangerous Tools |\n * |--------|------------|-----------------|-----------------|\n * | yolo | \u2713 allow | \u2713 allow | \u2713 allow |\n * | smart | \u2713 allow | \u2713 allow | \u26A0 ask |\n * | strict | \u2713 allow | \u26A0 ask | \u26A0 ask |\n */\nfunction shouldAllowByRiskLevel(\n safetyMode: SafetyMode,\n riskLevel: RiskLevel,\n): boolean {\n switch (safetyMode) {\n case 'yolo':\n // YOLO mode: allow everything\n return true\n case 'smart':\n // Smart mode: allow safe and monitored, ask for dangerous\n return riskLevel === 'safe' || riskLevel === 'monitored'\n case 'strict':\n // Strict mode: only allow safe tools\n return riskLevel === 'safe'\n }\n}\n\nexport const hasPermissionsToUseTool: CanUseToolFn = async (\n tool,\n input,\n context,\n _assistantMessage,\n): Promise<PermissionResult> => {\n const safetyMode = getEffectiveSafetyMode(context.options)\n\n // Get risk level for this tool (with command-specific classification for Bash)\n const riskLevel = getToolRiskLevel(\n tool.name,\n input as { command?: string } | undefined,\n )\n\n // Check if this tool should be auto-allowed based on safety mode and risk level\n if (shouldAllowByRiskLevel(safetyMode, riskLevel)) {\n return { result: true }\n }\n\n if (context.abortController.signal.aborted) {\n throw new AbortError()\n }\n\n // Check if the tool needs permissions\n try {\n if (!tool.needsPermissions(input as never)) {\n return { result: true }\n }\n } catch (e) {\n logError(`Error checking permissions: ${e}`)\n return { result: false, message: 'Error checking permissions' }\n }\n\n const projectConfig = getCurrentProjectConfig()\n const allowedTools = projectConfig.allowedTools ?? []\n // Special case for BashTool to allow blanket commands without exposing them in the UI\n if (tool === BashTool && allowedTools.includes(BashTool.name)) {\n return { result: true }\n }\n\n // TODO: Move this into tool definitions (done for read tools!)\n switch (tool) {\n // For bash tool, check each sub-command's permissions separately\n case BashTool: {\n // The types have already been validated by the tool,\n // so we can safely parse the input (as opposed to safeParse).\n const { command } = inputSchema.parse(input)\n return await bashToolHasPermission(tool, command, context, allowedTools)\n }\n // For file editing tools, check session-only permissions\n case FileEditTool:\n case FileWriteTool:\n case NotebookEditTool: {\n // The types have already been validated by the tool,\n // so we can safely pass this in\n if (!tool.needsPermissions(input)) {\n return { result: true }\n }\n return {\n result: false,\n message: `${PRODUCT_NAME} requested permissions to use ${tool.name}, but you haven't granted it yet.`,\n }\n }\n // For other tools, check persistent permissions\n default: {\n const permissionKey = getPermissionKey(tool, input, null)\n if (allowedTools.includes(permissionKey)) {\n return { result: true }\n }\n\n return {\n result: false,\n message: `${PRODUCT_NAME} requested permissions to use ${tool.name}, but you haven't granted it yet.`,\n }\n }\n }\n}\n\nexport async function savePermission(\n tool: Tool,\n input: { [k: string]: unknown },\n prefix: string | null,\n): Promise<void> {\n const key = getPermissionKey(tool, input, prefix)\n\n // For file editing tools, store write permissions only in memory\n if (\n tool === FileEditTool ||\n tool === FileWriteTool ||\n tool === NotebookEditTool\n ) {\n grantWritePermissionForOriginalDir()\n return\n }\n\n // For other tools, store permissions on disk\n const projectConfig = getCurrentProjectConfig()\n if (projectConfig.allowedTools.includes(key)) {\n return\n }\n\n projectConfig.allowedTools.push(key)\n projectConfig.allowedTools.sort()\n\n saveCurrentProjectConfig(projectConfig)\n}\n\nfunction getPermissionKey(\n tool: Tool,\n input: { [k: string]: unknown },\n prefix: string | null,\n): string {\n switch (tool) {\n case BashTool:\n if (prefix) {\n return `${BashTool.name}(${prefix}:*)`\n }\n return `${BashTool.name}(${BashTool.renderToolUseMessage(input as never)})`\n default:\n return tool.name\n }\n}\n"],
5
- "mappings": "AAEA,SAAS,UAAU,mBAAmB;AACtC,SAAS,oBAAoB;AAC7B,SAAS,qBAAqB;AAC9B,SAAS,wBAAwB;AACjC,SAAS,4BAA4B,oBAAoB;AACzD;AAAA,EACE;AAAA,EACA;AAAA,OAEK;AACP,SAAS,kBAAkB;AAC3B,SAAS,gBAAgB;AACzB,SAAS,0CAA0C;AACnD,SAAS,cAAc;AACvB,SAAS,oBAAoB;AAC7B;AAAA,EACE;AAAA,OAEK;AAGP,MAAM,gBAAgB,oBAAI,IAAI;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAEM,MAAM,yCAAyC,CACpD,MACA,SACA,iBACY;AACZ,MAAI,cAAc,IAAI,OAAO,GAAG;AAC9B,WAAO;AAAA,EACT;AAEA,MAAI,aAAa,SAAS,iBAAiB,MAAM,EAAE,QAAQ,GAAG,IAAI,CAAC,GAAG;AACpE,WAAO;AAAA,EACT;AAEA,MAAI,aAAa,SAAS,iBAAiB,MAAM,EAAE,QAAQ,GAAG,OAAO,CAAC,GAAG;AACvE,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEO,MAAM,+BAA+B,CAC1C,MACA,SACA,QACA,iBACY;AAEZ,MAAI,uCAAuC,MAAM,SAAS,YAAY,GAAG;AACvE,WAAO;AAAA,EACT;AACA,SAAO,aAAa,SAAS,iBAAiB,MAAM,EAAE,QAAQ,GAAG,MAAM,CAAC;AAC1E;AAEO,MAAM,wBAAwB,OACnC,MACA,SACA,SACA,cACA,+BAA+B,+BACD;AAC9B,MAAI,uCAAuC,MAAM,SAAS,YAAY,GAAG;AAEvE,WAAO,EAAE,QAAQ,KAAK;AAAA,EACxB;AAEA,QAAM,cAAc,aAAa,OAAO,EAAE,OAAO,OAAK;AAEpD,QAAI,MAAM,MAAM,OAAO,CAAC,IAAI;AAC1B,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT,CAAC;AACD,QAAM,0BAA0B,MAAM;AAAA,IACpC;AAAA,IACA,QAAQ,gBAAgB;AAAA,EAC1B;AACA,MAAI,QAAQ,gBAAgB,OAAO,SAAS;AAC1C,UAAM,IAAI,WAAW;AAAA,EACvB;AAEA,MAAI,4BAA4B,MAAM;AAGpC,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,SAAS,GAAG,YAAY,iCAAiC,KAAK,IAAI;AAAA,IACpE;AAAA,EACF;AAEA,MAAI,wBAAwB,0BAA0B;AAEpD,QAAI,uCAAuC,MAAM,SAAS,YAAY,GAAG;AACvE,aAAO,EAAE,QAAQ,KAAK;AAAA,IACxB,OAAO;AACL,aAAO;AAAA,QACL,QAAQ;AAAA,QACR,SAAS,GAAG,YAAY,iCAAiC,KAAK,IAAI;AAAA,MACpE;AAAA,IACF;AAAA,EACF;AAKA,QAAM,gBACJ,mBAAmB,0BACf,wBAAwB,gBACxB;AAGN,MAAI,YAAY,SAAS,GAAG;AAC1B,QACE,6BAA6B,MAAM,SAAS,eAAe,YAAY,GACvE;AACA,aAAO,EAAE,QAAQ,KAAK;AAAA,IACxB,OAAO;AACL,aAAO;AAAA,QACL,QAAQ;AAAA,QACR,SAAS,GAAG,YAAY,iCAAiC,KAAK,IAAI;AAAA,MACpE;AAAA,IACF;AAAA,EACF;AACA,MACE,YAAY,MAAM,gBAAc;AAC9B,UAAM,eACJ,wBAAwB,mBAAmB,IAAI,UAAU;AAC3D,QAAI,iBAAiB,UAAa,aAAa,0BAA0B;AAEvE,aAAO;AAAA,IACT;AAGA,UAAM,mBACJ,mBAAmB,eAAe,aAAa,gBAAgB;AACjE,UAAM,gBAAgB;AAAA,MACpB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,WAAO;AAAA,EACT,CAAC,GACD;AACA,WAAO,EAAE,QAAQ,KAAK;AAAA,EACxB;AACA,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,SAAS,GAAG,YAAY,iCAAiC,KAAK,IAAI;AAAA,EACpE;AACF;AAQA,SAAS,uBAAuB,SAGjB;AAEb,MAAI,QAAQ,YAAY;AACtB,WAAO,QAAQ;AAAA,EACjB;AAIA,MAAI,QAAQ,aAAa,MAAM;AAC7B,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAYA,SAAS,uBACP,YACA,WACS;AACT,UAAQ,YAAY;AAAA,IAClB,KAAK;AAEH,aAAO;AAAA,IACT,KAAK;AAEH,aAAO,cAAc,UAAU,cAAc;AAAA,IAC/C,KAAK;AAEH,aAAO,cAAc;AAAA,EACzB;AACF;AAEO,MAAM,0BAAwC,OACnD,MACA,OACA,SACA,sBAC8B;AAC9B,QAAM,aAAa,uBAAuB,QAAQ,OAAO;AAGzD,QAAM,YAAY;AAAA,IAChB,KAAK;AAAA,IACL;AAAA,EACF;AAGA,MAAI,uBAAuB,YAAY,SAAS,GAAG;AACjD,WAAO,EAAE,QAAQ,KAAK;AAAA,EACxB;AAEA,MAAI,QAAQ,gBAAgB,OAAO,SAAS;AAC1C,UAAM,IAAI,WAAW;AAAA,EACvB;AAGA,MAAI;AACF,QAAI,CAAC,KAAK,iBAAiB,KAAc,GAAG;AAC1C,aAAO,EAAE,QAAQ,KAAK;AAAA,IACxB;AAAA,EACF,SAAS,GAAG;AACV,aAAS,+BAA+B,CAAC,EAAE;AAC3C,WAAO,EAAE,QAAQ,OAAO,SAAS,6BAA6B;AAAA,EAChE;AAEA,QAAM,gBAAgB,wBAAwB;AAC9C,QAAM,eAAe,cAAc,gBAAgB,CAAC;AAEpD,MAAI,SAAS,YAAY,aAAa,SAAS,SAAS,IAAI,GAAG;AAC7D,WAAO,EAAE,QAAQ,KAAK;AAAA,EACxB;AAGA,UAAQ,MAAM;AAAA;AAAA,IAEZ,KAAK,UAAU;AAGb,YAAM,EAAE,QAAQ,IAAI,YAAY,MAAM,KAAK;AAC3C,aAAO,MAAM,sBAAsB,MAAM,SAAS,SAAS,YAAY;AAAA,IACzE;AAAA;AAAA,IAEA,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK,kBAAkB;AAGrB,UAAI,CAAC,KAAK,iBAAiB,KAAK,GAAG;AACjC,eAAO,EAAE,QAAQ,KAAK;AAAA,MACxB;AACA,aAAO;AAAA,QACL,QAAQ;AAAA,QACR,SAAS,GAAG,YAAY,iCAAiC,KAAK,IAAI;AAAA,MACpE;AAAA,IACF;AAAA;AAAA,IAEA,SAAS;AACP,YAAM,gBAAgB,iBAAiB,MAAM,OAAO,IAAI;AACxD,UAAI,aAAa,SAAS,aAAa,GAAG;AACxC,eAAO,EAAE,QAAQ,KAAK;AAAA,MACxB;AAEA,aAAO;AAAA,QACL,QAAQ;AAAA,QACR,SAAS,GAAG,YAAY,iCAAiC,KAAK,IAAI;AAAA,MACpE;AAAA,IACF;AAAA,EACF;AACF;AAEA,eAAsB,eACpB,MACA,OACA,QACe;AACf,QAAM,MAAM,iBAAiB,MAAM,OAAO,MAAM;AAGhD,MACE,SAAS,gBACT,SAAS,iBACT,SAAS,kBACT;AACA,uCAAmC;AACnC;AAAA,EACF;AAGA,QAAM,gBAAgB,wBAAwB;AAC9C,MAAI,cAAc,aAAa,SAAS,GAAG,GAAG;AAC5C;AAAA,EACF;AAEA,gBAAc,aAAa,KAAK,GAAG;AACnC,gBAAc,aAAa,KAAK;AAEhC,2BAAyB,aAAa;AACxC;AAEA,SAAS,iBACP,MACA,OACA,QACQ;AACR,UAAQ,MAAM;AAAA,IACZ,KAAK;AACH,UAAI,QAAQ;AACV,eAAO,GAAG,SAAS,IAAI,IAAI,MAAM;AAAA,MACnC;AACA,aAAO,GAAG,SAAS,IAAI,IAAI,SAAS,qBAAqB,KAAc,CAAC;AAAA,IAC1E;AACE,aAAO,KAAK;AAAA,EAChB;AACF;",
4
+ "sourcesContent": ["import type { CanUseToolFn } from './hooks/useCanUseTool'\nimport { Tool, ToolUseContext } from './Tool'\nimport { BashTool, inputSchema } from './tools/BashTool/BashTool'\nimport { FileEditTool } from './tools/FileEditTool/FileEditTool'\nimport { FileWriteTool } from './tools/FileWriteTool/FileWriteTool'\nimport { NotebookEditTool } from './tools/NotebookEditTool/NotebookEditTool'\nimport { getCommandSubcommandPrefix, splitCommand } from './utils/commands'\nimport {\n getCurrentProjectConfig,\n saveCurrentProjectConfig,\n type SafetyMode,\n} from '@utils/config'\nimport { AbortError } from './utils/errors'\nimport { logError } from './utils/log'\nimport { grantWritePermissionForOriginalDir } from './utils/permissions/filesystem'\nimport { getCwd } from './utils/state'\nimport { PRODUCT_NAME } from './constants/product'\nimport {\n getToolRiskLevel,\n type RiskLevel,\n} from './utils/toolRiskClassification'\nimport { resolve, relative, isAbsolute } from 'path'\n\n// Commands that are known to be safe for execution\nconst SAFE_COMMANDS = new Set([\n 'git status',\n 'git diff',\n 'git log',\n 'git branch',\n 'pwd',\n 'tree',\n 'date',\n 'which',\n])\n\nexport const bashToolCommandHasExactMatchPermission = (\n tool: Tool,\n command: string,\n allowedTools: string[],\n): boolean => {\n if (SAFE_COMMANDS.has(command)) {\n return true\n }\n // Check exact match first\n if (allowedTools.includes(getPermissionKey(tool, { command }, null))) {\n return true\n }\n // Check if command is an exact match with an approved prefix\n if (allowedTools.includes(getPermissionKey(tool, { command }, command))) {\n return true\n }\n return false\n}\n\nexport const bashToolCommandHasPermission = (\n tool: Tool,\n command: string,\n prefix: string | null,\n allowedTools: string[],\n): boolean => {\n // Check exact match first\n if (bashToolCommandHasExactMatchPermission(tool, command, allowedTools)) {\n return true\n }\n return allowedTools.includes(getPermissionKey(tool, { command }, prefix))\n}\n\nexport const bashToolHasPermission = async (\n tool: Tool,\n command: string,\n context: ToolUseContext,\n allowedTools: string[],\n getCommandSubcommandPrefixFn = getCommandSubcommandPrefix,\n): Promise<PermissionResult> => {\n if (bashToolCommandHasExactMatchPermission(tool, command, allowedTools)) {\n // This is an exact match for a command that is allowed, so we can skip the prefix check\n return { result: true }\n }\n\n const subCommands = splitCommand(command).filter(_ => {\n // Denim likes to add this, we strip it out so we don't need to prompt the user each time\n if (_ === `cd ${getCwd()}`) {\n return false\n }\n return true\n })\n const commandSubcommandPrefix = await getCommandSubcommandPrefixFn(\n command,\n context.abortController.signal,\n )\n if (context.abortController.signal.aborted) {\n throw new AbortError()\n }\n\n if (commandSubcommandPrefix === null) {\n // Fail closed and ask for user approval if the command prefix query failed (e.g. due to network error)\n // This is NOT the same as `fullCommandPrefix.commandPrefix === null`, which means no prefix was detected\n return {\n result: false,\n message: `${PRODUCT_NAME} requested permissions to use ${tool.name}, but you haven't granted it yet.`,\n }\n }\n\n if (commandSubcommandPrefix.commandInjectionDetected) {\n // Only allow exact matches for potential command injections\n if (bashToolCommandHasExactMatchPermission(tool, command, allowedTools)) {\n return { result: true }\n } else {\n return {\n result: false,\n message: `${PRODUCT_NAME} requested permissions to use ${tool.name}, but you haven't granted it yet.`,\n }\n }\n }\n\n // After the commandInjectionDetected check above, TypeScript still sees the union type.\n // We know commandInjectionDetected is false here, so commandPrefix exists.\n // Use 'in' check for proper type narrowing\n const commandPrefix =\n 'commandPrefix' in commandSubcommandPrefix\n ? commandSubcommandPrefix.commandPrefix\n : null\n\n // If there is only one command, no need to process subCommands\n if (subCommands.length < 2) {\n if (\n bashToolCommandHasPermission(tool, command, commandPrefix, allowedTools)\n ) {\n return { result: true }\n } else {\n return {\n result: false,\n message: `${PRODUCT_NAME} requested permissions to use ${tool.name}, but you haven't granted it yet.`,\n }\n }\n }\n if (\n subCommands.every(subCommand => {\n const prefixResult =\n commandSubcommandPrefix.subcommandPrefixes.get(subCommand)\n if (prefixResult === undefined || prefixResult.commandInjectionDetected) {\n // If prefix result is missing or command injection is detected, always ask for permission\n return false\n }\n // After the check above, we know commandInjectionDetected is false\n // Use 'in' check for proper type narrowing\n const subCommandPrefix =\n 'commandPrefix' in prefixResult ? prefixResult.commandPrefix : null\n const hasPermission = bashToolCommandHasPermission(\n tool,\n subCommand,\n subCommandPrefix,\n allowedTools,\n )\n return hasPermission\n })\n ) {\n return { result: true }\n }\n return {\n result: false,\n message: `${PRODUCT_NAME} requested permissions to use ${tool.name}, but you haven't granted it yet.`,\n }\n}\n\ntype PermissionResult = { result: true } | { result: false; message: string }\n\n/**\n * Get effective safety mode from context options\n * Handles backward compatibility with legacy safeMode boolean\n */\nfunction getEffectiveSafetyMode(options: {\n safeMode?: boolean\n safetyMode?: SafetyMode\n}): SafetyMode {\n // New safetyMode takes precedence\n if (options.safetyMode) {\n return options.safetyMode\n }\n // Backward compatibility: convert legacy safeMode boolean\n // safeMode: true \u2192 'strict' (old behavior)\n // safeMode: false \u2192 'yolo' (permissive)\n if (options.safeMode === true) {\n return 'strict'\n }\n // Default to 'yolo' for non-technical users (zero interruption)\n return 'yolo'\n}\n\n/**\n * Check if a path is within the project directory\n * Used by Free mode to allow operations within project boundaries\n */\nfunction isPathWithinProject(\n targetPath: string,\n projectDir: string,\n): boolean {\n // Resolve to absolute path\n const absolutePath = isAbsolute(targetPath)\n ? resolve(targetPath)\n : resolve(projectDir, targetPath)\n\n // Get relative path from project directory\n const relativePath = relative(projectDir, absolutePath)\n\n // Path is within project if:\n // 1. It doesn't start with '..' (not escaping project)\n // 2. It's not an absolute path (after relative calculation)\n return !relativePath.startsWith('..') && !isAbsolute(relativePath)\n}\n\n/**\n * System critical paths that should NEVER be auto-allowed, even in Free mode\n * These paths require explicit user confirmation regardless of safety mode\n */\nconst SYSTEM_CRITICAL_PATHS = [\n '/etc',\n '/bin',\n '/sbin',\n '/usr/bin',\n '/usr/sbin',\n '/var',\n '/System',\n '/Library',\n '/Applications',\n '/private',\n 'C:\\\\Windows',\n 'C:\\\\Program Files',\n 'C:\\\\Program Files (x86)',\n]\n\n/**\n * Check if a path is a system critical path\n */\nfunction isSystemCriticalPath(targetPath: string): boolean {\n const normalizedPath = resolve(targetPath).toLowerCase()\n return SYSTEM_CRITICAL_PATHS.some(\n criticalPath => normalizedPath.startsWith(criticalPath.toLowerCase())\n )\n}\n\n/**\n * Extract paths from tool input for boundary checking in Free mode\n */\nfunction extractPathsFromToolInput(\n toolName: string,\n input: { [k: string]: unknown },\n): string[] {\n const paths: string[] = []\n\n // File tools\n if (toolName === 'Read' || toolName === 'Write' || toolName === 'Edit') {\n if (typeof input.file_path === 'string') {\n paths.push(input.file_path)\n }\n }\n\n // Glob/Grep tools\n if (toolName === 'Glob' || toolName === 'Grep') {\n if (typeof input.path === 'string') {\n paths.push(input.path)\n }\n }\n\n // LS tool\n if (toolName === 'LS') {\n if (typeof input.path === 'string') {\n paths.push(input.path)\n }\n }\n\n // NotebookEdit\n if (toolName === 'NotebookEdit') {\n if (typeof input.notebook_path === 'string') {\n paths.push(input.notebook_path)\n }\n }\n\n return paths\n}\n\n/**\n * Extract paths from a Bash command for boundary checking\n * This is a simplified extraction - complex commands may need manual review\n */\nfunction extractPathsFromBashCommand(command: string): string[] {\n const paths: string[] = []\n\n // Extract quoted paths\n const quotedMatches = command.matchAll(/[\"']([^\"']+)[\"']/g)\n for (const match of quotedMatches) {\n if (match[1] && (match[1].includes('/') || match[1].startsWith('.'))) {\n paths.push(match[1])\n }\n }\n\n // Extract unquoted paths (starting with ./ or / or ../)\n const unquotedMatches = command.matchAll(\n /(?:^|\\s)((?:\\.\\/|\\/|\\.\\.\\/)[^\\s;&|>]+)/g\n )\n for (const match of unquotedMatches) {\n if (match[1]) {\n paths.push(match[1])\n }\n }\n\n // Extract redirect targets\n const redirectMatches = command.matchAll(/>\\s*[\"']?([^\\s\"';&|]+)[\"']?/g)\n for (const match of redirectMatches) {\n if (match[1] && !match[1].startsWith('&')) {\n paths.push(match[1])\n }\n }\n\n return [...new Set(paths)] // Deduplicate\n}\n\n/**\n * Check if all paths in an operation are within the project directory\n * Used by Free mode for path-based permission decisions\n */\nfunction areAllPathsWithinProject(\n toolName: string,\n input: { [k: string]: unknown },\n projectDir: string,\n): { withinProject: boolean; outsidePaths: string[] } {\n let paths: string[] = []\n\n // For Bash tool, extract paths from command\n if (toolName === 'Bash' && typeof input.command === 'string') {\n paths = extractPathsFromBashCommand(input.command)\n } else {\n paths = extractPathsFromToolInput(toolName, input)\n }\n\n // If no paths detected, allow by default (e.g., pwd, date, etc.)\n if (paths.length === 0) {\n return { withinProject: true, outsidePaths: [] }\n }\n\n const outsidePaths: string[] = []\n for (const path of paths) {\n // Check system critical paths first\n if (isSystemCriticalPath(path)) {\n outsidePaths.push(path)\n continue\n }\n\n // Check if path is within project\n if (!isPathWithinProject(path, projectDir)) {\n outsidePaths.push(path)\n }\n }\n\n return {\n withinProject: outsidePaths.length === 0,\n outsidePaths,\n }\n}\n\n/**\n * Check if a tool should be allowed based on safety mode and risk level\n *\n * Safety Mode Matrix:\n * | Mode | Safe Tools | Monitored Tools | Dangerous Tools |\n * |--------|------------|-----------------|-----------------|\n * | yolo | \u2713 allow | \u2713 allow | \u2713 allow |\n * | smart | \u2713 allow | \u2713 allow | \u26A0 ask |\n * | strict | \u2713 allow | \u26A0 ask | \u26A0 ask |\n * | free | Path-based: within project = allow, outside = ask |\n */\nfunction shouldAllowByRiskLevel(\n safetyMode: SafetyMode,\n riskLevel: RiskLevel,\n toolName?: string,\n input?: { [k: string]: unknown },\n): boolean {\n switch (safetyMode) {\n case 'yolo':\n // YOLO mode: allow everything\n return true\n case 'smart':\n // Smart mode: allow safe and monitored, ask for dangerous\n return riskLevel === 'safe' || riskLevel === 'monitored'\n case 'strict':\n // Strict mode: only allow safe tools\n return riskLevel === 'safe'\n case 'free':\n // Free mode: path-based decision\n // Safe tools (read-only) are always allowed\n if (riskLevel === 'safe') {\n return true\n }\n // For tools with side effects, check path boundaries\n if (toolName && input) {\n const projectDir = getCwd()\n const { withinProject } = areAllPathsWithinProject(\n toolName,\n input,\n projectDir,\n )\n return withinProject\n }\n // If we can't determine paths, ask for confirmation\n return false\n }\n}\n\nexport const hasPermissionsToUseTool: CanUseToolFn = async (\n tool,\n input,\n context,\n _assistantMessage,\n): Promise<PermissionResult> => {\n const safetyMode = getEffectiveSafetyMode(context.options)\n\n // Get risk level for this tool (with command-specific classification for Bash)\n const riskLevel = getToolRiskLevel(\n tool.name,\n input as { command?: string } | undefined,\n )\n\n // Check if this tool should be auto-allowed based on safety mode and risk level\n // For Free mode, we also pass tool name and input for path-based decisions\n if (shouldAllowByRiskLevel(\n safetyMode,\n riskLevel,\n tool.name,\n input as { [k: string]: unknown },\n )) {\n return { result: true }\n }\n\n if (context.abortController.signal.aborted) {\n throw new AbortError()\n }\n\n // Check if the tool needs permissions\n try {\n if (!tool.needsPermissions(input as never)) {\n return { result: true }\n }\n } catch (e) {\n logError(`Error checking permissions: ${e}`)\n return { result: false, message: 'Error checking permissions' }\n }\n\n const projectConfig = getCurrentProjectConfig()\n const allowedTools = projectConfig.allowedTools ?? []\n // Special case for BashTool to allow blanket commands without exposing them in the UI\n if (tool === BashTool && allowedTools.includes(BashTool.name)) {\n return { result: true }\n }\n\n // TODO: Move this into tool definitions (done for read tools!)\n switch (tool) {\n // For bash tool, check each sub-command's permissions separately\n case BashTool: {\n // The types have already been validated by the tool,\n // so we can safely parse the input (as opposed to safeParse).\n const { command } = inputSchema.parse(input)\n return await bashToolHasPermission(tool, command, context, allowedTools)\n }\n // For file editing tools, check session-only permissions\n case FileEditTool:\n case FileWriteTool:\n case NotebookEditTool: {\n // The types have already been validated by the tool,\n // so we can safely pass this in\n if (!tool.needsPermissions(input)) {\n return { result: true }\n }\n return {\n result: false,\n message: `${PRODUCT_NAME} requested permissions to use ${tool.name}, but you haven't granted it yet.`,\n }\n }\n // For other tools, check persistent permissions\n default: {\n const permissionKey = getPermissionKey(tool, input, null)\n if (allowedTools.includes(permissionKey)) {\n return { result: true }\n }\n\n return {\n result: false,\n message: `${PRODUCT_NAME} requested permissions to use ${tool.name}, but you haven't granted it yet.`,\n }\n }\n }\n}\n\nexport async function savePermission(\n tool: Tool,\n input: { [k: string]: unknown },\n prefix: string | null,\n): Promise<void> {\n const key = getPermissionKey(tool, input, prefix)\n\n // For file editing tools, store write permissions only in memory\n if (\n tool === FileEditTool ||\n tool === FileWriteTool ||\n tool === NotebookEditTool\n ) {\n grantWritePermissionForOriginalDir()\n return\n }\n\n // For other tools, store permissions on disk\n const projectConfig = getCurrentProjectConfig()\n if (projectConfig.allowedTools.includes(key)) {\n return\n }\n\n projectConfig.allowedTools.push(key)\n projectConfig.allowedTools.sort()\n\n saveCurrentProjectConfig(projectConfig)\n}\n\nfunction getPermissionKey(\n tool: Tool,\n input: { [k: string]: unknown },\n prefix: string | null,\n): string {\n switch (tool) {\n case BashTool:\n if (prefix) {\n return `${BashTool.name}(${prefix}:*)`\n }\n return `${BashTool.name}(${BashTool.renderToolUseMessage(input as never)})`\n default:\n return tool.name\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -5,6 +5,7 @@ import { deserializeMessages } from "../utils/conversationRecovery.js";
5
5
  import { LogSelector } from "../components/LogSelector.js";
6
6
  import { logError, getNextAvailableLogForkNumber } from "../utils/log.js";
7
7
  import { isDefaultSlowAndCapableModel } from "../utils/model.js";
8
+ import { prepareTerminalForREPL } from "../utils/terminal.js";
8
9
  function ResumeConversation({
9
10
  context,
10
11
  commands,
@@ -20,6 +21,7 @@ function ResumeConversation({
20
21
  try {
21
22
  context.unmount?.();
22
23
  const isDefaultModel = await isDefaultSlowAndCapableModel();
24
+ await prepareTerminalForREPL();
23
25
  render(
24
26
  /* @__PURE__ */ React.createElement(
25
27
  REPL,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/screens/ResumeConversation.tsx"],
4
- "sourcesContent": ["import React from 'react'\nimport { render } from 'ink'\nimport { REPL } from './REPL'\nimport { deserializeMessages } from '@utils/conversationRecovery'\nimport { LogSelector } from '@components/LogSelector'\nimport type { LogOption } from '@minto-types/logs'\nimport { logError, getNextAvailableLogForkNumber } from '@utils/log'\nimport type { Tool } from '@tool'\nimport { Command } from '@commands'\nimport { isDefaultSlowAndCapableModel } from '@utils/model'\n\ntype Props = {\n commands: Command[]\n context: { unmount?: () => void }\n logs: LogOption[]\n tools: Tool[]\n verbose: boolean | undefined\n}\n\nexport function ResumeConversation({\n context,\n commands,\n logs,\n tools,\n verbose,\n}: Props): React.ReactNode {\n async function onSelect(index: number) {\n const log = logs[index]\n if (!log) {\n return\n }\n\n // Load and deserialize the messages\n try {\n context.unmount?.()\n // Start a new REPL with the loaded messages\n // Increment the fork number by 1 to generate a new transcript\n // Check if using default model before rendering\n const isDefaultModel = await isDefaultSlowAndCapableModel()\n\n render(\n <REPL\n messageLogName={log.date}\n initialPrompt=\"\"\n shouldShowPromptInput={true}\n verbose={verbose}\n commands={commands}\n tools={tools}\n initialMessages={deserializeMessages(log.messages, tools)}\n initialForkNumber={getNextAvailableLogForkNumber(\n log.date,\n log.forkNumber ?? 1,\n 0,\n )}\n isDefaultModel={isDefaultModel}\n isResumedConversation={true}\n />,\n {\n exitOnCtrlC: false,\n },\n )\n } catch (e) {\n logError(`Failed to load conversation: ${e}`)\n throw e\n }\n }\n\n return <LogSelector logs={logs} onSelect={onSelect} />\n}\n"],
5
- "mappings": "AAAA,OAAO,WAAW;AAClB,SAAS,cAAc;AACvB,SAAS,YAAY;AACrB,SAAS,2BAA2B;AACpC,SAAS,mBAAmB;AAE5B,SAAS,UAAU,qCAAqC;AAGxD,SAAS,oCAAoC;AAUtC,SAAS,mBAAmB;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA2B;AACzB,iBAAe,SAAS,OAAe;AACrC,UAAM,MAAM,KAAK,KAAK;AACtB,QAAI,CAAC,KAAK;AACR;AAAA,IACF;AAGA,QAAI;AACF,cAAQ,UAAU;AAIlB,YAAM,iBAAiB,MAAM,6BAA6B;AAE1D;AAAA,QACE;AAAA,UAAC;AAAA;AAAA,YACC,gBAAgB,IAAI;AAAA,YACpB,eAAc;AAAA,YACd,uBAAuB;AAAA,YACvB;AAAA,YACA;AAAA,YACA;AAAA,YACA,iBAAiB,oBAAoB,IAAI,UAAU,KAAK;AAAA,YACxD,mBAAmB;AAAA,cACjB,IAAI;AAAA,cACJ,IAAI,cAAc;AAAA,cAClB;AAAA,YACF;AAAA,YACA;AAAA,YACA,uBAAuB;AAAA;AAAA,QACzB;AAAA,QACA;AAAA,UACE,aAAa;AAAA,QACf;AAAA,MACF;AAAA,IACF,SAAS,GAAG;AACV,eAAS,gCAAgC,CAAC,EAAE;AAC5C,YAAM;AAAA,IACR;AAAA,EACF;AAEA,SAAO,oCAAC,eAAY,MAAY,UAAoB;AACtD;",
4
+ "sourcesContent": ["import React from 'react'\nimport { render } from 'ink'\nimport { REPL } from './REPL'\nimport { deserializeMessages } from '@utils/conversationRecovery'\nimport { LogSelector } from '@components/LogSelector'\nimport type { LogOption } from '@minto-types/logs'\nimport { logError, getNextAvailableLogForkNumber } from '@utils/log'\nimport type { Tool } from '@tool'\nimport { Command } from '@commands'\nimport { isDefaultSlowAndCapableModel } from '@utils/model'\nimport { prepareTerminalForREPL } from '@utils/terminal'\n\ntype Props = {\n commands: Command[]\n context: { unmount?: () => void }\n logs: LogOption[]\n tools: Tool[]\n verbose: boolean | undefined\n}\n\nexport function ResumeConversation({\n context,\n commands,\n logs,\n tools,\n verbose,\n}: Props): React.ReactNode {\n async function onSelect(index: number) {\n const log = logs[index]\n if (!log) {\n return\n }\n\n // Load and deserialize the messages\n try {\n context.unmount?.()\n // Start a new REPL with the loaded messages\n // Increment the fork number by 1 to generate a new transcript\n // Check if using default model before rendering\n const isDefaultModel = await isDefaultSlowAndCapableModel()\n\n // Clear screen and prepare terminal for REPL\n await prepareTerminalForREPL()\n\n render(\n <REPL\n messageLogName={log.date}\n initialPrompt=\"\"\n shouldShowPromptInput={true}\n verbose={verbose}\n commands={commands}\n tools={tools}\n initialMessages={deserializeMessages(log.messages, tools)}\n initialForkNumber={getNextAvailableLogForkNumber(\n log.date,\n log.forkNumber ?? 1,\n 0,\n )}\n isDefaultModel={isDefaultModel}\n isResumedConversation={true}\n />,\n {\n exitOnCtrlC: false,\n },\n )\n } catch (e) {\n logError(`Failed to load conversation: ${e}`)\n throw e\n }\n }\n\n return <LogSelector logs={logs} onSelect={onSelect} />\n}\n"],
5
+ "mappings": "AAAA,OAAO,WAAW;AAClB,SAAS,cAAc;AACvB,SAAS,YAAY;AACrB,SAAS,2BAA2B;AACpC,SAAS,mBAAmB;AAE5B,SAAS,UAAU,qCAAqC;AAGxD,SAAS,oCAAoC;AAC7C,SAAS,8BAA8B;AAUhC,SAAS,mBAAmB;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA2B;AACzB,iBAAe,SAAS,OAAe;AACrC,UAAM,MAAM,KAAK,KAAK;AACtB,QAAI,CAAC,KAAK;AACR;AAAA,IACF;AAGA,QAAI;AACF,cAAQ,UAAU;AAIlB,YAAM,iBAAiB,MAAM,6BAA6B;AAG1D,YAAM,uBAAuB;AAE7B;AAAA,QACE;AAAA,UAAC;AAAA;AAAA,YACC,gBAAgB,IAAI;AAAA,YACpB,eAAc;AAAA,YACd,uBAAuB;AAAA,YACvB;AAAA,YACA;AAAA,YACA;AAAA,YACA,iBAAiB,oBAAoB,IAAI,UAAU,KAAK;AAAA,YACxD,mBAAmB;AAAA,cACjB,IAAI;AAAA,cACJ,IAAI,cAAc;AAAA,cAClB;AAAA,YACF;AAAA,YACA;AAAA,YACA,uBAAuB;AAAA;AAAA,QACzB;AAAA,QACA;AAAA,UACE,aAAa;AAAA,QACf;AAAA,MACF;AAAA,IACF,SAAS,GAAG;AACV,eAAS,gCAAgC,CAAC,EAAE;AAC5C,YAAM;AAAA,IACR;AAAA,EACF;AAEA,SAAO,oCAAC,eAAY,MAAY,UAAoB;AACtD;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,205 @@
1
+ import { getSessionState, setSessionState } from "../utils/sessionState.js";
2
+ import { emitReminderEvent } from "./systemReminder.js";
3
+ const TASK_STORAGE_KEY = "claudeCodeTasks";
4
+ let taskCache = null;
5
+ let cacheTimestamp = 0;
6
+ const CACHE_TTL = 5e3;
7
+ let nextTaskId = 1;
8
+ function invalidateCache() {
9
+ taskCache = null;
10
+ cacheTimestamp = 0;
11
+ }
12
+ function getNextTaskId() {
13
+ const tasks = getAllTasks();
14
+ const existingIds = tasks.map((t) => parseInt(t.id, 10)).filter((id) => !isNaN(id));
15
+ if (existingIds.length > 0) {
16
+ nextTaskId = Math.max(...existingIds) + 1;
17
+ }
18
+ return String(nextTaskId++);
19
+ }
20
+ function generateActiveForm(subject) {
21
+ const trimmed = subject.trim();
22
+ const patterns = [
23
+ { regex: /^(Run|run)\s+(.+)$/i, replacement: "Running $2" },
24
+ { regex: /^(Build|build)\s+(.+)$/i, replacement: "Building $2" },
25
+ { regex: /^(Fix|fix)\s+(.+)$/i, replacement: "Fixing $2" },
26
+ { regex: /^(Add|add)\s+(.+)$/i, replacement: "Adding $2" },
27
+ { regex: /^(Create|create)\s+(.+)$/i, replacement: "Creating $2" },
28
+ { regex: /^(Update|update)\s+(.+)$/i, replacement: "Updating $2" },
29
+ { regex: /^(Delete|delete)\s+(.+)$/i, replacement: "Deleting $2" },
30
+ { regex: /^(Test|test)\s+(.+)$/i, replacement: "Testing $2" },
31
+ { regex: /^(Deploy|deploy)\s+(.+)$/i, replacement: "Deploying $2" },
32
+ { regex: /^(Analyze|analyze)\s+(.+)$/i, replacement: "Analyzing $2" },
33
+ { regex: /^(Review|review)\s+(.+)$/i, replacement: "Reviewing $2" },
34
+ { regex: /^(Write|write)\s+(.+)$/i, replacement: "Writing $2" },
35
+ {
36
+ regex: /^(Implement|implement)\s+(.+)$/i,
37
+ replacement: "Implementing $2"
38
+ },
39
+ { regex: /^(Refactor|refactor)\s+(.+)$/i, replacement: "Refactoring $2" },
40
+ { regex: /^(Debug|debug)\s+(.+)$/i, replacement: "Debugging $2" },
41
+ { regex: /^(Configure|configure)\s+(.+)$/i, replacement: "Configuring $2" },
42
+ { regex: /^(Install|install)\s+(.+)$/i, replacement: "Installing $2" },
43
+ { regex: /^(Remove|remove)\s+(.+)$/i, replacement: "Removing $2" },
44
+ { regex: /^(Verify|verify)\s+(.+)$/i, replacement: "Verifying $2" },
45
+ { regex: /^(Check|check)\s+(.+)$/i, replacement: "Checking $2" }
46
+ ];
47
+ for (const { regex, replacement } of patterns) {
48
+ if (regex.test(trimmed)) {
49
+ return trimmed.replace(regex, replacement);
50
+ }
51
+ }
52
+ return `Working on: ${trimmed}`;
53
+ }
54
+ function getAllTasks() {
55
+ const now = Date.now();
56
+ if (taskCache && now - cacheTimestamp < CACHE_TTL) {
57
+ return taskCache;
58
+ }
59
+ const sessionState = getSessionState();
60
+ const tasks = sessionState[TASK_STORAGE_KEY] || [];
61
+ taskCache = [...tasks];
62
+ cacheTimestamp = now;
63
+ return tasks;
64
+ }
65
+ function saveTasks(tasks) {
66
+ setSessionState({
67
+ ...getSessionState(),
68
+ [TASK_STORAGE_KEY]: tasks
69
+ });
70
+ invalidateCache();
71
+ }
72
+ function createTask(input) {
73
+ const tasks = getAllTasks();
74
+ const now = Date.now();
75
+ const newTask = {
76
+ id: getNextTaskId(),
77
+ subject: input.subject,
78
+ description: input.description,
79
+ status: "pending",
80
+ activeForm: input.activeForm || generateActiveForm(input.subject),
81
+ metadata: input.metadata,
82
+ createdAt: now,
83
+ updatedAt: now
84
+ };
85
+ const updatedTasks = [...tasks, newTask];
86
+ saveTasks(updatedTasks);
87
+ emitReminderEvent("task:created", {
88
+ task: newTask,
89
+ timestamp: now
90
+ });
91
+ return newTask;
92
+ }
93
+ function updateTask(input) {
94
+ const tasks = getAllTasks();
95
+ const taskIndex = tasks.findIndex((t) => t.id === input.taskId);
96
+ if (taskIndex === -1) {
97
+ return null;
98
+ }
99
+ const existingTask = tasks[taskIndex];
100
+ const now = Date.now();
101
+ if (input.status === "deleted") {
102
+ const updatedTasks2 = tasks.filter((t) => t.id !== input.taskId);
103
+ saveTasks(updatedTasks2);
104
+ emitReminderEvent("task:deleted", {
105
+ taskId: input.taskId,
106
+ timestamp: now
107
+ });
108
+ return existingTask;
109
+ }
110
+ const updatedTask = {
111
+ ...existingTask,
112
+ updatedAt: now
113
+ };
114
+ if (input.status !== void 0) {
115
+ updatedTask.status = input.status;
116
+ }
117
+ if (input.subject !== void 0) {
118
+ updatedTask.subject = input.subject;
119
+ }
120
+ if (input.description !== void 0) {
121
+ updatedTask.description = input.description;
122
+ }
123
+ if (input.activeForm !== void 0) {
124
+ updatedTask.activeForm = input.activeForm;
125
+ }
126
+ if (input.owner !== void 0) {
127
+ updatedTask.owner = input.owner;
128
+ }
129
+ if (input.metadata !== void 0) {
130
+ const newMetadata = { ...existingTask.metadata };
131
+ for (const [key, value] of Object.entries(input.metadata)) {
132
+ if (value === null) {
133
+ delete newMetadata[key];
134
+ } else {
135
+ newMetadata[key] = value;
136
+ }
137
+ }
138
+ updatedTask.metadata = Object.keys(newMetadata).length > 0 ? newMetadata : void 0;
139
+ }
140
+ if (input.addBlocks && input.addBlocks.length > 0) {
141
+ const currentBlocks = new Set(existingTask.blocks || []);
142
+ input.addBlocks.forEach((id) => currentBlocks.add(id));
143
+ updatedTask.blocks = Array.from(currentBlocks);
144
+ }
145
+ if (input.addBlockedBy && input.addBlockedBy.length > 0) {
146
+ const currentBlockedBy = new Set(existingTask.blockedBy || []);
147
+ input.addBlockedBy.forEach((id) => currentBlockedBy.add(id));
148
+ updatedTask.blockedBy = Array.from(currentBlockedBy);
149
+ }
150
+ const updatedTasks = [...tasks];
151
+ updatedTasks[taskIndex] = updatedTask;
152
+ saveTasks(updatedTasks);
153
+ emitReminderEvent("task:updated", {
154
+ previousTask: existingTask,
155
+ updatedTask,
156
+ timestamp: now
157
+ });
158
+ return updatedTask;
159
+ }
160
+ function getTaskById(taskId) {
161
+ const tasks = getAllTasks();
162
+ return tasks.find((t) => t.id === taskId) || null;
163
+ }
164
+ function getTaskList() {
165
+ const tasks = getAllTasks();
166
+ return tasks.map((task) => {
167
+ const openBlockers = (task.blockedBy || []).filter((blockerId) => {
168
+ const blocker = tasks.find((t) => t.id === blockerId);
169
+ return blocker && blocker.status !== "completed";
170
+ });
171
+ return {
172
+ id: task.id,
173
+ subject: task.subject,
174
+ status: task.status,
175
+ owner: task.owner,
176
+ blockedBy: openBlockers.length > 0 ? openBlockers : void 0
177
+ };
178
+ });
179
+ }
180
+ function clearAllTasks() {
181
+ saveTasks([]);
182
+ nextTaskId = 1;
183
+ emitReminderEvent("task:cleared", {
184
+ timestamp: Date.now()
185
+ });
186
+ }
187
+ function getTaskStatistics() {
188
+ const tasks = getAllTasks();
189
+ return {
190
+ total: tasks.length,
191
+ pending: tasks.filter((t) => t.status === "pending").length,
192
+ inProgress: tasks.filter((t) => t.status === "in_progress").length,
193
+ completed: tasks.filter((t) => t.status === "completed").length
194
+ };
195
+ }
196
+ export {
197
+ clearAllTasks,
198
+ createTask,
199
+ getAllTasks,
200
+ getTaskById,
201
+ getTaskList,
202
+ getTaskStatistics,
203
+ updateTask
204
+ };
205
+ //# sourceMappingURL=taskStore.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/services/taskStore.ts"],
4
+ "sourcesContent": ["/**\n * Task Store Service - Claude Code Compatible\n *\n * Provides task management functionality compatible with Claude Code's\n * TaskCreate, TaskUpdate, TaskGet, TaskList tools.\n *\n * Task lifecycle: pending \u2192 in_progress \u2192 completed\n * Tasks can also be deleted (soft delete with 'deleted' status)\n */\n\nimport { getSessionState, setSessionState } from '@utils/sessionState'\nimport { emitReminderEvent } from '@services/systemReminder'\n\n/**\n * Task interface matching Claude Code's task structure\n */\nexport interface Task {\n id: string\n subject: string\n description: string\n status: 'pending' | 'in_progress' | 'completed'\n activeForm?: string\n owner?: string\n metadata?: Record<string, unknown>\n blocks?: string[] // Task IDs that this task blocks\n blockedBy?: string[] // Task IDs that block this task\n createdAt: number\n updatedAt: number\n}\n\n/**\n * Task creation input (without auto-generated fields)\n */\nexport interface TaskCreateInput {\n subject: string\n description: string\n activeForm?: string\n metadata?: Record<string, unknown>\n}\n\n/**\n * Task update input (all fields optional except taskId)\n */\nexport interface TaskUpdateInput {\n taskId: string\n status?: 'pending' | 'in_progress' | 'completed' | 'deleted'\n subject?: string\n description?: string\n activeForm?: string\n owner?: string\n metadata?: Record<string, unknown>\n addBlocks?: string[]\n addBlockedBy?: string[]\n}\n\nconst TASK_STORAGE_KEY = 'claudeCodeTasks'\n\n// In-memory cache\nlet taskCache: Task[] | null = null\nlet cacheTimestamp = 0\nconst CACHE_TTL = 5000 // 5 seconds\n\n// Auto-incrementing ID counter\nlet nextTaskId = 1\n\nfunction invalidateCache(): void {\n taskCache = null\n cacheTimestamp = 0\n}\n\nfunction getNextTaskId(): string {\n // Find the highest existing ID to avoid conflicts\n const tasks = getAllTasks()\n const existingIds = tasks\n .map(t => parseInt(t.id, 10))\n .filter(id => !isNaN(id))\n\n if (existingIds.length > 0) {\n nextTaskId = Math.max(...existingIds) + 1\n }\n\n return String(nextTaskId++)\n}\n\n/**\n * Generate activeForm from subject if not provided\n * Converts imperative form to present continuous\n */\nfunction generateActiveForm(subject: string): string {\n const trimmed = subject.trim()\n\n const patterns = [\n { regex: /^(Run|run)\\s+(.+)$/i, replacement: 'Running $2' },\n { regex: /^(Build|build)\\s+(.+)$/i, replacement: 'Building $2' },\n { regex: /^(Fix|fix)\\s+(.+)$/i, replacement: 'Fixing $2' },\n { regex: /^(Add|add)\\s+(.+)$/i, replacement: 'Adding $2' },\n { regex: /^(Create|create)\\s+(.+)$/i, replacement: 'Creating $2' },\n { regex: /^(Update|update)\\s+(.+)$/i, replacement: 'Updating $2' },\n { regex: /^(Delete|delete)\\s+(.+)$/i, replacement: 'Deleting $2' },\n { regex: /^(Test|test)\\s+(.+)$/i, replacement: 'Testing $2' },\n { regex: /^(Deploy|deploy)\\s+(.+)$/i, replacement: 'Deploying $2' },\n { regex: /^(Analyze|analyze)\\s+(.+)$/i, replacement: 'Analyzing $2' },\n { regex: /^(Review|review)\\s+(.+)$/i, replacement: 'Reviewing $2' },\n { regex: /^(Write|write)\\s+(.+)$/i, replacement: 'Writing $2' },\n {\n regex: /^(Implement|implement)\\s+(.+)$/i,\n replacement: 'Implementing $2',\n },\n { regex: /^(Refactor|refactor)\\s+(.+)$/i, replacement: 'Refactoring $2' },\n { regex: /^(Debug|debug)\\s+(.+)$/i, replacement: 'Debugging $2' },\n { regex: /^(Configure|configure)\\s+(.+)$/i, replacement: 'Configuring $2' },\n { regex: /^(Install|install)\\s+(.+)$/i, replacement: 'Installing $2' },\n { regex: /^(Remove|remove)\\s+(.+)$/i, replacement: 'Removing $2' },\n { regex: /^(Verify|verify)\\s+(.+)$/i, replacement: 'Verifying $2' },\n { regex: /^(Check|check)\\s+(.+)$/i, replacement: 'Checking $2' },\n ]\n\n for (const { regex, replacement } of patterns) {\n if (regex.test(trimmed)) {\n return trimmed.replace(regex, replacement)\n }\n }\n\n return `Working on: ${trimmed}`\n}\n\n/**\n * Get all tasks from storage\n */\nexport function getAllTasks(): Task[] {\n const now = Date.now()\n\n // Check cache first\n if (taskCache && now - cacheTimestamp < CACHE_TTL) {\n return taskCache\n }\n\n const sessionState = getSessionState() as Record<string, unknown>\n const tasks = (sessionState[TASK_STORAGE_KEY] as Task[]) || []\n\n // Update cache\n taskCache = [...tasks]\n cacheTimestamp = now\n\n return tasks\n}\n\n/**\n * Save tasks to storage\n */\nfunction saveTasks(tasks: Task[]): void {\n setSessionState({\n ...getSessionState(),\n [TASK_STORAGE_KEY]: tasks,\n } as Record<string, unknown>)\n\n invalidateCache()\n}\n\n/**\n * Create a new task\n */\nexport function createTask(input: TaskCreateInput): Task {\n const tasks = getAllTasks()\n const now = Date.now()\n\n const newTask: Task = {\n id: getNextTaskId(),\n subject: input.subject,\n description: input.description,\n status: 'pending',\n activeForm: input.activeForm || generateActiveForm(input.subject),\n metadata: input.metadata,\n createdAt: now,\n updatedAt: now,\n }\n\n const updatedTasks = [...tasks, newTask]\n saveTasks(updatedTasks)\n\n // Emit event for system reminders\n emitReminderEvent('task:created', {\n task: newTask,\n timestamp: now,\n })\n\n return newTask\n}\n\n/**\n * Update an existing task\n */\nexport function updateTask(input: TaskUpdateInput): Task | null {\n const tasks = getAllTasks()\n const taskIndex = tasks.findIndex(t => t.id === input.taskId)\n\n if (taskIndex === -1) {\n return null\n }\n\n const existingTask = tasks[taskIndex]\n const now = Date.now()\n\n // Handle deletion\n if (input.status === 'deleted') {\n const updatedTasks = tasks.filter(t => t.id !== input.taskId)\n saveTasks(updatedTasks)\n\n emitReminderEvent('task:deleted', {\n taskId: input.taskId,\n timestamp: now,\n })\n\n return existingTask\n }\n\n // Build updated task\n const updatedTask: Task = {\n ...existingTask,\n updatedAt: now,\n }\n\n if (input.status !== undefined) {\n updatedTask.status = input.status\n }\n if (input.subject !== undefined) {\n updatedTask.subject = input.subject\n }\n if (input.description !== undefined) {\n updatedTask.description = input.description\n }\n if (input.activeForm !== undefined) {\n updatedTask.activeForm = input.activeForm\n }\n if (input.owner !== undefined) {\n updatedTask.owner = input.owner\n }\n if (input.metadata !== undefined) {\n // Merge metadata, null values delete keys\n const newMetadata = { ...existingTask.metadata }\n for (const [key, value] of Object.entries(input.metadata)) {\n if (value === null) {\n delete newMetadata[key]\n } else {\n newMetadata[key] = value\n }\n }\n updatedTask.metadata =\n Object.keys(newMetadata).length > 0 ? newMetadata : undefined\n }\n\n // Handle dependency updates\n if (input.addBlocks && input.addBlocks.length > 0) {\n const currentBlocks = new Set(existingTask.blocks || [])\n input.addBlocks.forEach(id => currentBlocks.add(id))\n updatedTask.blocks = Array.from(currentBlocks)\n }\n if (input.addBlockedBy && input.addBlockedBy.length > 0) {\n const currentBlockedBy = new Set(existingTask.blockedBy || [])\n input.addBlockedBy.forEach(id => currentBlockedBy.add(id))\n updatedTask.blockedBy = Array.from(currentBlockedBy)\n }\n\n // Update tasks array\n const updatedTasks = [...tasks]\n updatedTasks[taskIndex] = updatedTask\n saveTasks(updatedTasks)\n\n // Emit event\n emitReminderEvent('task:updated', {\n previousTask: existingTask,\n updatedTask,\n timestamp: now,\n })\n\n return updatedTask\n}\n\n/**\n * Get a task by ID\n */\nexport function getTaskById(taskId: string): Task | null {\n const tasks = getAllTasks()\n return tasks.find(t => t.id === taskId) || null\n}\n\n/**\n * Get task list summary for display\n * Returns tasks with summary info suitable for listing\n */\nexport function getTaskList(): Array<{\n id: string\n subject: string\n status: 'pending' | 'in_progress' | 'completed'\n owner?: string\n blockedBy?: string[]\n}> {\n const tasks = getAllTasks()\n\n // Filter out tasks blocked by incomplete tasks\n return tasks.map(task => {\n // Check which blockedBy tasks are still open\n const openBlockers = (task.blockedBy || []).filter(blockerId => {\n const blocker = tasks.find(t => t.id === blockerId)\n return blocker && blocker.status !== 'completed'\n })\n\n return {\n id: task.id,\n subject: task.subject,\n status: task.status,\n owner: task.owner,\n blockedBy: openBlockers.length > 0 ? openBlockers : undefined,\n }\n })\n}\n\n/**\n * Clear all tasks (for testing or reset)\n */\nexport function clearAllTasks(): void {\n saveTasks([])\n nextTaskId = 1\n\n emitReminderEvent('task:cleared', {\n timestamp: Date.now(),\n })\n}\n\n/**\n * Get task statistics\n */\nexport function getTaskStatistics(): {\n total: number\n pending: number\n inProgress: number\n completed: number\n} {\n const tasks = getAllTasks()\n\n return {\n total: tasks.length,\n pending: tasks.filter(t => t.status === 'pending').length,\n inProgress: tasks.filter(t => t.status === 'in_progress').length,\n completed: tasks.filter(t => t.status === 'completed').length,\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["updatedTasks"]
7
+ }
@@ -17,7 +17,15 @@ const inputSchema = z.strictObject({
17
17
  ).min(2).max(4).optional().describe("List of options (2-4 recommended)"),
18
18
  multiSelect: z.boolean().optional().default(false).describe("Allow multiple selections")
19
19
  })
20
- ).min(1).max(4).describe("Questions to ask (1-4 questions)")
20
+ ).min(1).max(4).describe("Questions to ask (1-4 questions)"),
21
+ answers: z.record(z.string(), z.string()).optional().describe("User answers collected by the permission component"),
22
+ metadata: z.object({
23
+ source: z.string().optional().describe(
24
+ 'Optional identifier for the source of this question (e.g., "remember" for /remember command). Used for analytics tracking.'
25
+ )
26
+ }).optional().describe(
27
+ "Optional metadata for tracking and analytics purposes. Not displayed to user."
28
+ )
21
29
  });
22
30
  const AskUserQuestionTool = {
23
31
  name: "AskUserQuestion",
@@ -74,7 +82,35 @@ ${formattedAnswers}`;
74
82
  return /* @__PURE__ */ React.createElement(Box, { key: index, flexDirection: "row", paddingLeft: 4 }, /* @__PURE__ */ React.createElement(Text, { color: "#6B7280" }, "\u2022 "), answer.customInput ? /* @__PURE__ */ React.createElement(Text, null, "Q", (answer.questionIndex ?? index) + 1, ': "', answer.customInput, '"') : answer.selectedOptions ? /* @__PURE__ */ React.createElement(Text, null, "Q", (answer.questionIndex ?? index) + 1, ": Option", " ", answer.selectedOptions.map((i) => i + 1).join(", ")) : /* @__PURE__ */ React.createElement(Text, { color: SEMANTIC_COLORS.dim }, "Q", (answer.questionIndex ?? index) + 1, ": No answer"));
75
83
  }));
76
84
  },
77
- async *call({ questions }, context) {
85
+ async *call({
86
+ questions,
87
+ answers: providedAnswers,
88
+ metadata
89
+ }, context) {
90
+ if (providedAnswers) {
91
+ const convertedAnswers = Object.entries(
92
+ providedAnswers
93
+ ).map(([key, value], index) => ({
94
+ questionIndex: index,
95
+ customInput: value
96
+ }));
97
+ yield {
98
+ type: "result",
99
+ data: {
100
+ answers: convertedAnswers,
101
+ timestamp: Date.now(),
102
+ ...metadata && { metadata }
103
+ },
104
+ resultForAssistant: `User answered:
105
+ ${convertedAnswers.map((answer) => {
106
+ if (answer.customInput) {
107
+ return `Q${answer.questionIndex + 1}: "${answer.customInput}"`;
108
+ }
109
+ return `Q${answer.questionIndex + 1}: No answer`;
110
+ }).join("\n")}`
111
+ };
112
+ return;
113
+ }
78
114
  if (!context.askUser) {
79
115
  throw new Error(
80
116
  "AskUserQuestion tool requires askUser function in context"
@@ -95,7 +131,8 @@ ${formattedAnswers}`;
95
131
  type: "result",
96
132
  data: {
97
133
  answers,
98
- timestamp: Date.now()
134
+ timestamp: Date.now(),
135
+ ...metadata && { metadata }
99
136
  },
100
137
  resultForAssistant: `User answered:
101
138
  ${answers.map((answer) => {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/tools/AskUserQuestionTool/AskUserQuestionTool.tsx"],
4
- "sourcesContent": ["import { Box, Text } from 'ink'\nimport * as React from 'react'\nimport { z } from 'zod'\nimport { Tool, type ExtendedToolUseContext } from '@tool'\nimport { FallbackToolUseRejectedMessage } from '@components/FallbackToolUseRejectedMessage'\nimport { DESCRIPTION, PROMPT } from './prompt'\nimport type { UserAnswer } from '@minto-types/askUserQuestion'\nimport { SEMANTIC_COLORS } from '@constants/colors'\n\nconst inputSchema = z.strictObject({\n questions: z\n .array(\n z.object({\n question: z.string().describe('The question to ask the user'),\n header: z\n .string()\n .max(12)\n .optional()\n .describe('Short category label (max 12 chars)'),\n options: z\n .array(\n z.object({\n label: z.string().describe('Option display text'),\n description: z.string().optional().describe('Option explanation'),\n }),\n )\n .min(2)\n .max(4)\n .optional()\n .describe('List of options (2-4 recommended)'),\n multiSelect: z\n .boolean()\n .optional()\n .default(false)\n .describe('Allow multiple selections'),\n }),\n )\n .min(1)\n .max(4)\n .describe('Questions to ask (1-4 questions)'),\n})\n\nexport type AskUserQuestionOutput = {\n answers: UserAnswer[]\n timestamp: number\n}\n\nexport const AskUserQuestionTool = {\n name: 'AskUserQuestion',\n\n async description() {\n return DESCRIPTION\n },\n\n async prompt() {\n return PROMPT\n },\n\n inputSchema,\n\n userFacingName() {\n return 'Ask User Question'\n },\n\n async isEnabled() {\n return true\n },\n\n isReadOnly() {\n return true // Doesn't modify files\n },\n\n isConcurrencySafe() {\n return false // User interaction is not concurrent-safe\n },\n\n needsPermissions() {\n return false // No special permissions needed\n },\n\n renderResultForAssistant(output: AskUserQuestionOutput) {\n const formattedAnswers = output.answers\n .map(answer => {\n if (answer.customInput) {\n return `Q${answer.questionIndex + 1}: \"${answer.customInput}\"`\n }\n if (answer.selectedOptions) {\n return `Q${answer.questionIndex + 1}: Option ${answer.selectedOptions.map(i => i + 1).join(', ')}`\n }\n return `Q${answer.questionIndex + 1}: No answer`\n })\n .join('\\n')\n\n return `User answered:\\n${formattedAnswers}`\n },\n\n renderToolUseMessage(input: z.infer<typeof inputSchema>) {\n const questionCount = input.questions.length\n const questionText =\n questionCount === 1\n ? input.questions[0]!.question\n : `${questionCount} questions`\n return `Asking user: ${questionText}`\n },\n\n renderToolUseRejectedMessage() {\n return <FallbackToolUseRejectedMessage />\n },\n\n renderToolResultMessage(output: AskUserQuestionOutput) {\n // Guard against undefined or null output\n if (!output) {\n return (\n <Box flexDirection=\"column\" paddingLeft={2}>\n <Box flexDirection=\"row\">\n <Text color=\"#10B981\">{' '}\u23BF </Text>\n <Text>Question completed</Text>\n </Box>\n </Box>\n )\n }\n\n const answers = output.answers || []\n\n return (\n <Box flexDirection=\"column\" paddingLeft={2}>\n <Box flexDirection=\"row\">\n <Text color=\"#10B981\">{' '}\u23BF </Text>\n <Text>User provided answers</Text>\n </Box>\n {answers.map((answer, index) => {\n // Guard against null/undefined answers\n if (!answer) return null\n return (\n <Box key={index} flexDirection=\"row\" paddingLeft={4}>\n <Text color=\"#6B7280\">\u2022 </Text>\n {answer.customInput ? (\n <Text>\n Q{(answer.questionIndex ?? index) + 1}: \"{answer.customInput}\"\n </Text>\n ) : answer.selectedOptions ? (\n <Text>\n Q{(answer.questionIndex ?? index) + 1}: Option{' '}\n {answer.selectedOptions.map(i => i + 1).join(', ')}\n </Text>\n ) : (\n <Text color={SEMANTIC_COLORS.dim}>\n Q{(answer.questionIndex ?? index) + 1}: No answer\n </Text>\n )}\n </Box>\n )\n })}\n </Box>\n )\n },\n\n async *call(\n { questions }: z.infer<typeof inputSchema>,\n context: ExtendedToolUseContext,\n ) {\n // Check if askUser function is available\n if (!context.askUser) {\n throw new Error(\n 'AskUserQuestion tool requires askUser function in context',\n )\n }\n\n try {\n // Convert parsed questions to UserQuestion type\n const userQuestions = questions.map(q => ({\n question: q.question,\n header: q.header,\n options: q.options?.map(opt => ({\n label: opt.label,\n description: opt.description,\n })),\n multiSelect: q.multiSelect ?? false,\n }))\n\n // Call askUser and wait for user response\n const answers = await context.askUser(userQuestions)\n\n // Return the result\n yield {\n type: 'result',\n data: {\n answers,\n timestamp: Date.now(),\n },\n resultForAssistant: `User answered:\\n${answers\n .map(answer => {\n if (answer.customInput) {\n return `Q${answer.questionIndex + 1}: \"${answer.customInput}\"`\n }\n if (answer.selectedOptions) {\n return `Q${answer.questionIndex + 1}: Option ${answer.selectedOptions.map(i => i + 1).join(', ')}`\n }\n return `Q${answer.questionIndex + 1}: No answer`\n })\n .join('\\n')}`,\n }\n } catch (error) {\n // User cancelled or error occurred\n throw new Error(\n `Failed to get user response: ${error instanceof Error ? error.message : 'Unknown error'}`,\n )\n }\n },\n} satisfies Tool<typeof inputSchema, AskUserQuestionOutput>\n"],
5
- "mappings": "AAAA,SAAS,KAAK,YAAY;AAC1B,YAAY,WAAW;AACvB,SAAS,SAAS;AAElB,SAAS,sCAAsC;AAC/C,SAAS,aAAa,cAAc;AAEpC,SAAS,uBAAuB;AAEhC,MAAM,cAAc,EAAE,aAAa;AAAA,EACjC,WAAW,EACR;AAAA,IACC,EAAE,OAAO;AAAA,MACP,UAAU,EAAE,OAAO,EAAE,SAAS,8BAA8B;AAAA,MAC5D,QAAQ,EACL,OAAO,EACP,IAAI,EAAE,EACN,SAAS,EACT,SAAS,qCAAqC;AAAA,MACjD,SAAS,EACN;AAAA,QACC,EAAE,OAAO;AAAA,UACP,OAAO,EAAE,OAAO,EAAE,SAAS,qBAAqB;AAAA,UAChD,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,oBAAoB;AAAA,QAClE,CAAC;AAAA,MACH,EACC,IAAI,CAAC,EACL,IAAI,CAAC,EACL,SAAS,EACT,SAAS,mCAAmC;AAAA,MAC/C,aAAa,EACV,QAAQ,EACR,SAAS,EACT,QAAQ,KAAK,EACb,SAAS,2BAA2B;AAAA,IACzC,CAAC;AAAA,EACH,EACC,IAAI,CAAC,EACL,IAAI,CAAC,EACL,SAAS,kCAAkC;AAChD,CAAC;AAOM,MAAM,sBAAsB;AAAA,EACjC,MAAM;AAAA,EAEN,MAAM,cAAc;AAClB,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,SAAS;AACb,WAAO;AAAA,EACT;AAAA,EAEA;AAAA,EAEA,iBAAiB;AACf,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,YAAY;AAChB,WAAO;AAAA,EACT;AAAA,EAEA,aAAa;AACX,WAAO;AAAA,EACT;AAAA,EAEA,oBAAoB;AAClB,WAAO;AAAA,EACT;AAAA,EAEA,mBAAmB;AACjB,WAAO;AAAA,EACT;AAAA,EAEA,yBAAyB,QAA+B;AACtD,UAAM,mBAAmB,OAAO,QAC7B,IAAI,YAAU;AACb,UAAI,OAAO,aAAa;AACtB,eAAO,IAAI,OAAO,gBAAgB,CAAC,MAAM,OAAO,WAAW;AAAA,MAC7D;AACA,UAAI,OAAO,iBAAiB;AAC1B,eAAO,IAAI,OAAO,gBAAgB,CAAC,YAAY,OAAO,gBAAgB,IAAI,OAAK,IAAI,CAAC,EAAE,KAAK,IAAI,CAAC;AAAA,MAClG;AACA,aAAO,IAAI,OAAO,gBAAgB,CAAC;AAAA,IACrC,CAAC,EACA,KAAK,IAAI;AAEZ,WAAO;AAAA,EAAmB,gBAAgB;AAAA,EAC5C;AAAA,EAEA,qBAAqB,OAAoC;AACvD,UAAM,gBAAgB,MAAM,UAAU;AACtC,UAAM,eACJ,kBAAkB,IACd,MAAM,UAAU,CAAC,EAAG,WACpB,GAAG,aAAa;AACtB,WAAO,gBAAgB,YAAY;AAAA,EACrC;AAAA,EAEA,+BAA+B;AAC7B,WAAO,oCAAC,oCAA+B;AAAA,EACzC;AAAA,EAEA,wBAAwB,QAA+B;AAErD,QAAI,CAAC,QAAQ;AACX,aACE,oCAAC,OAAI,eAAc,UAAS,aAAa,KACvC,oCAAC,OAAI,eAAc,SACjB,oCAAC,QAAK,OAAM,aAAW,MAAK,SAAE,GAC9B,oCAAC,YAAK,oBAAkB,CAC1B,CACF;AAAA,IAEJ;AAEA,UAAM,UAAU,OAAO,WAAW,CAAC;AAEnC,WACE,oCAAC,OAAI,eAAc,UAAS,aAAa,KACvC,oCAAC,OAAI,eAAc,SACjB,oCAAC,QAAK,OAAM,aAAW,MAAK,SAAE,GAC9B,oCAAC,YAAK,uBAAqB,CAC7B,GACC,QAAQ,IAAI,CAAC,QAAQ,UAAU;AAE9B,UAAI,CAAC,OAAQ,QAAO;AACpB,aACE,oCAAC,OAAI,KAAK,OAAO,eAAc,OAAM,aAAa,KAChD,oCAAC,QAAK,OAAM,aAAU,SAAE,GACvB,OAAO,cACN,oCAAC,YAAK,MACD,OAAO,iBAAiB,SAAS,GAAE,OAAI,OAAO,aAAY,GAC/D,IACE,OAAO,kBACT,oCAAC,YAAK,MACD,OAAO,iBAAiB,SAAS,GAAE,YAAS,KAC9C,OAAO,gBAAgB,IAAI,OAAK,IAAI,CAAC,EAAE,KAAK,IAAI,CACnD,IAEA,oCAAC,QAAK,OAAO,gBAAgB,OAAK,MAC7B,OAAO,iBAAiB,SAAS,GAAE,aACxC,CAEJ;AAAA,IAEJ,CAAC,CACH;AAAA,EAEJ;AAAA,EAEA,OAAO,KACL,EAAE,UAAU,GACZ,SACA;AAEA,QAAI,CAAC,QAAQ,SAAS;AACpB,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,QAAI;AAEF,YAAM,gBAAgB,UAAU,IAAI,QAAM;AAAA,QACxC,UAAU,EAAE;AAAA,QACZ,QAAQ,EAAE;AAAA,QACV,SAAS,EAAE,SAAS,IAAI,UAAQ;AAAA,UAC9B,OAAO,IAAI;AAAA,UACX,aAAa,IAAI;AAAA,QACnB,EAAE;AAAA,QACF,aAAa,EAAE,eAAe;AAAA,MAChC,EAAE;AAGF,YAAM,UAAU,MAAM,QAAQ,QAAQ,aAAa;AAGnD,YAAM;AAAA,QACJ,MAAM;AAAA,QACN,MAAM;AAAA,UACJ;AAAA,UACA,WAAW,KAAK,IAAI;AAAA,QACtB;AAAA,QACA,oBAAoB;AAAA,EAAmB,QACpC,IAAI,YAAU;AACb,cAAI,OAAO,aAAa;AACtB,mBAAO,IAAI,OAAO,gBAAgB,CAAC,MAAM,OAAO,WAAW;AAAA,UAC7D;AACA,cAAI,OAAO,iBAAiB;AAC1B,mBAAO,IAAI,OAAO,gBAAgB,CAAC,YAAY,OAAO,gBAAgB,IAAI,OAAK,IAAI,CAAC,EAAE,KAAK,IAAI,CAAC;AAAA,UAClG;AACA,iBAAO,IAAI,OAAO,gBAAgB,CAAC;AAAA,QACrC,CAAC,EACA,KAAK,IAAI,CAAC;AAAA,MACf;AAAA,IACF,SAAS,OAAO;AAEd,YAAM,IAAI;AAAA,QACR,gCAAgC,iBAAiB,QAAQ,MAAM,UAAU,eAAe;AAAA,MAC1F;AAAA,IACF;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["import { Box, Text } from 'ink'\nimport * as React from 'react'\nimport { z } from 'zod'\nimport { Tool, type ExtendedToolUseContext } from '@tool'\nimport { FallbackToolUseRejectedMessage } from '@components/FallbackToolUseRejectedMessage'\nimport { DESCRIPTION, PROMPT } from './prompt'\nimport type { UserAnswer } from '@minto-types/askUserQuestion'\nimport { SEMANTIC_COLORS } from '@constants/colors'\n\nconst inputSchema = z.strictObject({\n questions: z\n .array(\n z.object({\n question: z.string().describe('The question to ask the user'),\n header: z\n .string()\n .max(12)\n .optional()\n .describe('Short category label (max 12 chars)'),\n options: z\n .array(\n z.object({\n label: z.string().describe('Option display text'),\n description: z.string().optional().describe('Option explanation'),\n }),\n )\n .min(2)\n .max(4)\n .optional()\n .describe('List of options (2-4 recommended)'),\n multiSelect: z\n .boolean()\n .optional()\n .default(false)\n .describe('Allow multiple selections'),\n }),\n )\n .min(1)\n .max(4)\n .describe('Questions to ask (1-4 questions)'),\n answers: z\n .record(z.string(), z.string())\n .optional()\n .describe('User answers collected by the permission component'),\n metadata: z\n .object({\n source: z\n .string()\n .optional()\n .describe(\n 'Optional identifier for the source of this question (e.g., \"remember\" for /remember command). Used for analytics tracking.',\n ),\n })\n .optional()\n .describe(\n 'Optional metadata for tracking and analytics purposes. Not displayed to user.',\n ),\n})\n\nexport type AskUserQuestionOutput = {\n answers: UserAnswer[]\n timestamp: number\n metadata?: {\n source?: string\n }\n}\n\nexport const AskUserQuestionTool = {\n name: 'AskUserQuestion',\n\n async description() {\n return DESCRIPTION\n },\n\n async prompt() {\n return PROMPT\n },\n\n inputSchema,\n\n userFacingName() {\n return 'Ask User Question'\n },\n\n async isEnabled() {\n return true\n },\n\n isReadOnly() {\n return true // Doesn't modify files\n },\n\n isConcurrencySafe() {\n return false // User interaction is not concurrent-safe\n },\n\n needsPermissions() {\n return false // No special permissions needed\n },\n\n renderResultForAssistant(output: AskUserQuestionOutput) {\n const formattedAnswers = output.answers\n .map(answer => {\n if (answer.customInput) {\n return `Q${answer.questionIndex + 1}: \"${answer.customInput}\"`\n }\n if (answer.selectedOptions) {\n return `Q${answer.questionIndex + 1}: Option ${answer.selectedOptions.map(i => i + 1).join(', ')}`\n }\n return `Q${answer.questionIndex + 1}: No answer`\n })\n .join('\\n')\n\n return `User answered:\\n${formattedAnswers}`\n },\n\n renderToolUseMessage(input: z.infer<typeof inputSchema>) {\n const questionCount = input.questions.length\n const questionText =\n questionCount === 1\n ? input.questions[0]!.question\n : `${questionCount} questions`\n return `Asking user: ${questionText}`\n },\n\n renderToolUseRejectedMessage() {\n return <FallbackToolUseRejectedMessage />\n },\n\n renderToolResultMessage(output: AskUserQuestionOutput) {\n // Guard against undefined or null output\n if (!output) {\n return (\n <Box flexDirection=\"column\" paddingLeft={2}>\n <Box flexDirection=\"row\">\n <Text color=\"#10B981\">{' '}\u23BF </Text>\n <Text>Question completed</Text>\n </Box>\n </Box>\n )\n }\n\n const answers = output.answers || []\n\n return (\n <Box flexDirection=\"column\" paddingLeft={2}>\n <Box flexDirection=\"row\">\n <Text color=\"#10B981\">{' '}\u23BF </Text>\n <Text>User provided answers</Text>\n </Box>\n {answers.map((answer, index) => {\n // Guard against null/undefined answers\n if (!answer) return null\n return (\n <Box key={index} flexDirection=\"row\" paddingLeft={4}>\n <Text color=\"#6B7280\">\u2022 </Text>\n {answer.customInput ? (\n <Text>\n Q{(answer.questionIndex ?? index) + 1}: \"{answer.customInput}\"\n </Text>\n ) : answer.selectedOptions ? (\n <Text>\n Q{(answer.questionIndex ?? index) + 1}: Option{' '}\n {answer.selectedOptions.map(i => i + 1).join(', ')}\n </Text>\n ) : (\n <Text color={SEMANTIC_COLORS.dim}>\n Q{(answer.questionIndex ?? index) + 1}: No answer\n </Text>\n )}\n </Box>\n )\n })}\n </Box>\n )\n },\n\n async *call(\n {\n questions,\n answers: providedAnswers,\n metadata,\n }: z.infer<typeof inputSchema>,\n context: ExtendedToolUseContext,\n ) {\n // If answers are already provided (from permission component), use them directly\n if (providedAnswers) {\n // Convert provided answers (Record<string, string>) to UserAnswer[]\n const convertedAnswers: UserAnswer[] = Object.entries(\n providedAnswers,\n ).map(([key, value], index) => ({\n questionIndex: index,\n customInput: value,\n }))\n\n yield {\n type: 'result',\n data: {\n answers: convertedAnswers,\n timestamp: Date.now(),\n ...(metadata && { metadata }),\n },\n resultForAssistant: `User answered:\\n${convertedAnswers\n .map(answer => {\n if (answer.customInput) {\n return `Q${answer.questionIndex + 1}: \"${answer.customInput}\"`\n }\n return `Q${answer.questionIndex + 1}: No answer`\n })\n .join('\\n')}`,\n }\n return\n }\n\n // Check if askUser function is available\n if (!context.askUser) {\n throw new Error(\n 'AskUserQuestion tool requires askUser function in context',\n )\n }\n\n try {\n // Convert parsed questions to UserQuestion type\n const userQuestions = questions.map(q => ({\n question: q.question,\n header: q.header,\n options: q.options?.map(opt => ({\n label: opt.label,\n description: opt.description,\n })),\n multiSelect: q.multiSelect ?? false,\n }))\n\n // Call askUser and wait for user response\n const answers = await context.askUser(userQuestions)\n\n // Return the result\n yield {\n type: 'result',\n data: {\n answers,\n timestamp: Date.now(),\n ...(metadata && { metadata }),\n },\n resultForAssistant: `User answered:\\n${answers\n .map(answer => {\n if (answer.customInput) {\n return `Q${answer.questionIndex + 1}: \"${answer.customInput}\"`\n }\n if (answer.selectedOptions) {\n return `Q${answer.questionIndex + 1}: Option ${answer.selectedOptions.map(i => i + 1).join(', ')}`\n }\n return `Q${answer.questionIndex + 1}: No answer`\n })\n .join('\\n')}`,\n }\n } catch (error) {\n // User cancelled or error occurred\n throw new Error(\n `Failed to get user response: ${error instanceof Error ? error.message : 'Unknown error'}`,\n )\n }\n },\n} satisfies Tool<typeof inputSchema, AskUserQuestionOutput>\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -25,7 +25,20 @@ import { formatOutput, getCommandFilePaths } from "./utils.js";
25
25
  const inputSchema = z.strictObject({
26
26
  command: z.string().describe("The command to execute"),
27
27
  timeout: z.number().optional().describe("Optional timeout in milliseconds (max 600000)"),
28
- run_in_background: z.boolean().optional().describe("Set to true to run this command in the background")
28
+ run_in_background: z.boolean().optional().describe("Set to true to run this command in the background"),
29
+ description: z.string().optional().describe(
30
+ `Clear, concise description of what this command does in active voice. Never use words like "complex" or "risk" in the description - just describe what it does.
31
+
32
+ For simple commands (git, npm, standard CLI tools), keep it brief (5-10 words):
33
+ - ls \u2192 "List files in current directory"
34
+ - git status \u2192 "Show working tree status"
35
+ - npm install \u2192 "Install package dependencies"
36
+
37
+ For commands that are harder to parse at a glance (piped commands, obscure flags, etc.), add enough context to clarify what it does:
38
+ - find . -name "*.tmp" -exec rm {} \\; \u2192 "Find and delete all .tmp files recursively"
39
+ - git reset --hard origin/main \u2192 "Discard all local changes and match remote main"
40
+ - curl -s url | jq '.data[]' \u2192 "Fetch JSON from URL and extract data array elements"`
41
+ )
29
42
  });
30
43
  const BashTool = {
31
44
  name: "Bash",
@@ -111,7 +124,8 @@ const BashTool = {
111
124
  }
112
125
  return { result: true };
113
126
  },
114
- renderToolUseMessage({ command }) {
127
+ renderToolUseMessage({ command, description }) {
128
+ let displayCommand = command;
115
129
  if (command.includes(`"$(cat <<'EOF'`)) {
116
130
  const match = command.match(
117
131
  /^(.*?)"?\$\(cat <<'EOF'\n([\s\S]*?)\n\s*EOF\n\s*\)"(.*)$/
@@ -120,10 +134,13 @@ const BashTool = {
120
134
  const prefix = match[1];
121
135
  const content = match[2];
122
136
  const suffix = match[3] || "";
123
- return `${prefix.trim()} "${content.trim()}"${suffix.trim()}`;
137
+ displayCommand = `${prefix.trim()} "${content.trim()}"${suffix.trim()}`;
124
138
  }
125
139
  }
126
- return command;
140
+ if (description) {
141
+ return description;
142
+ }
143
+ return displayCommand;
127
144
  },
128
145
  renderToolUseRejectedMessage() {
129
146
  return /* @__PURE__ */ React.createElement(FallbackToolUseRejectedMessage, null);