@iaforged/context-code 2.0.1 → 2.0.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 (145) hide show
  1. package/dist/src/bridge/bridgeMain.js +1 -1
  2. package/dist/src/bridge/types.js +1 -1
  3. package/dist/src/cli/handlers/auth.js +1 -1
  4. package/dist/src/cli/handlers/mcp.js +1 -1
  5. package/dist/src/cli/print.js +1 -1
  6. package/dist/src/commands/copy/copy.js +1 -1
  7. package/dist/src/commands/heartbeat/heartbeat.js +1 -0
  8. package/dist/src/commands/heartbeat/index.js +1 -0
  9. package/dist/src/commands/insights.js +1 -1
  10. package/dist/src/commands/install-github-app/ExistingWorkflowStep.js +1 -1
  11. package/dist/src/commands/install-github-app/InstallAppStep.js +1 -1
  12. package/dist/src/commands/install-github-app/OAuthFlowStep.js +1 -1
  13. package/dist/src/commands/install-github-app/SuccessStep.js +1 -1
  14. package/dist/src/commands/install-github-app/setupGitHubActions.js +1 -1
  15. package/dist/src/commands/login-openai/index.js +1 -1
  16. package/dist/src/commands/mobile/mobile.js +1 -1
  17. package/dist/src/commands/remote-setup/remote-setup.js +1 -1
  18. package/dist/src/commands/swarm-auto/swarmAuto.js +1 -1
  19. package/dist/src/commands/timeline/timeline.js +1 -1
  20. package/dist/src/commands/webapp/webapp.js +1 -1
  21. package/dist/src/commands.js +1 -1
  22. package/dist/src/components/ApproveApiKey.js +1 -1
  23. package/dist/src/components/ChannelDowngradeDialog.js +1 -1
  24. package/dist/src/components/ClaudeCodeHint/PluginHintMenu.js +1 -1
  25. package/dist/src/components/ConsoleOAuthFlow.js +1 -1
  26. package/dist/src/components/CostThresholdDialog.js +1 -1
  27. package/dist/src/components/CtrlOToExpand.js +1 -1
  28. package/dist/src/components/CustomSelect/select.js +1 -1
  29. package/dist/src/components/DesktopHandoff.js +1 -1
  30. package/dist/src/components/DesktopUpsell/DesktopUpsellStartup.js +1 -1
  31. package/dist/src/components/ExportDialog.js +1 -1
  32. package/dist/src/components/Feedback.js +1 -1
  33. package/dist/src/components/FeedbackSurvey/FeedbackSurvey.js +1 -1
  34. package/dist/src/components/FeedbackSurvey/FeedbackSurveyView.js +1 -1
  35. package/dist/src/components/IdeAutoConnectDialog.js +1 -1
  36. package/dist/src/components/IdeOnboardingDialog.js +1 -1
  37. package/dist/src/components/IdleReturnDialog.js +1 -1
  38. package/dist/src/components/InterruptedByUser.js +1 -1
  39. package/dist/src/components/InvalidConfigDialog.js +1 -1
  40. package/dist/src/components/InvalidSettingsDialog.js +1 -1
  41. package/dist/src/components/LanguagePicker.js +1 -1
  42. package/dist/src/components/LogSelector.js +1 -1
  43. package/dist/src/components/LogoV2/ChannelsNotice.js +1 -1
  44. package/dist/src/components/LogoV2/LogoV2.js +1 -1
  45. package/dist/src/components/LogoV2/feedConfigs.js +1 -1
  46. package/dist/src/components/LspRecommendation/LspRecommendationMenu.js +1 -1
  47. package/dist/src/components/MCPServerApprovalDialog.js +1 -1
  48. package/dist/src/components/MCPServerDesktopImportDialog.js +1 -1
  49. package/dist/src/components/ManagedSettingsSecurityDialog/ManagedSettingsSecurityDialog.js +1 -1
  50. package/dist/src/components/MessageSelector.js +1 -1
  51. package/dist/src/components/OutputStylePicker.js +1 -1
  52. package/dist/src/components/PromptInput/PromptInput.js +1 -1
  53. package/dist/src/components/RemoteCallout.js +1 -1
  54. package/dist/src/components/ResumeTask.js +1 -1
  55. package/dist/src/components/Settings/Config.js +1 -1
  56. package/dist/src/components/ShowInIDEPrompt.js +1 -1
  57. package/dist/src/components/Stats.js +1 -1
  58. package/dist/src/components/TagTabs.js +1 -1
  59. package/dist/src/components/TeleportError.js +1 -1
  60. package/dist/src/components/TeleportResumeWrapper.js +1 -1
  61. package/dist/src/components/TextInput.js +1 -1
  62. package/dist/src/components/agents/new-agent-creation/wizard-steps/MethodStep.js +1 -1
  63. package/dist/src/components/design-system/KeyboardShortcutHint.js +1 -1
  64. package/dist/src/components/diff/DiffDetailView.js +1 -1
  65. package/dist/src/components/grove/Grove.js +1 -1
  66. package/dist/src/components/hooks/SelectEventMode.js +1 -1
  67. package/dist/src/components/hooks/SelectHookMode.js +1 -1
  68. package/dist/src/components/hooks/SelectMatcherMode.js +1 -1
  69. package/dist/src/components/hooks/ViewHookMode.js +1 -1
  70. package/dist/src/components/mcp/MCPAgentServerMenu.js +1 -1
  71. package/dist/src/components/mcp/utils/reconnectHelpers.js +1 -1
  72. package/dist/src/components/memory/MemoryFileSelector.js +1 -1
  73. package/dist/src/components/messages/AssistantTextMessage.js +1 -1
  74. package/dist/src/components/messages/PlanApprovalMessage.js +1 -1
  75. package/dist/src/components/messages/ShutdownMessage.js +1 -1
  76. package/dist/src/components/messages/SystemAPIErrorMessage.js +1 -1
  77. package/dist/src/components/messages/UserPlanMessage.js +1 -1
  78. package/dist/src/components/permissions/AskUserQuestionPermissionRequest/PreviewQuestionView.js +1 -1
  79. package/dist/src/components/permissions/ComputerUseApproval/ComputerUseApproval.js +1 -1
  80. package/dist/src/components/permissions/FileEditPermissionRequest/FileEditPermissionRequest.js +1 -1
  81. package/dist/src/components/permissions/FilePermissionDialog/FilePermissionDialog.js +1 -1
  82. package/dist/src/components/permissions/FilePermissionDialog/permissionOptions.js +1 -1
  83. package/dist/src/components/permissions/FileWritePermissionRequest/FileWritePermissionRequest.js +1 -1
  84. package/dist/src/components/permissions/FilesystemPermissionRequest/FilesystemPermissionRequest.js +1 -1
  85. package/dist/src/components/permissions/PermissionRequest.js +1 -1
  86. package/dist/src/components/permissions/SedEditPermissionRequest/SedEditPermissionRequest.js +1 -1
  87. package/dist/src/components/permissions/SkillPermissionRequest/SkillPermissionRequest.js +1 -1
  88. package/dist/src/components/permissions/WebFetchPermissionRequest/WebFetchPermissionRequest.js +1 -1
  89. package/dist/src/components/permissions/rules/AddPermissionRules.js +1 -1
  90. package/dist/src/components/permissions/rules/AddWorkspaceDirectory.js +1 -1
  91. package/dist/src/components/permissions/rules/PermissionRuleList.js +1 -1
  92. package/dist/src/components/permissions/rules/RemoveWorkspaceDirectory.js +1 -1
  93. package/dist/src/components/sandbox/SandboxConfigTab.js +1 -1
  94. package/dist/src/components/sandbox/SandboxOverridesTab.js +1 -1
  95. package/dist/src/components/tasks/RemoteSessionDetailDialog.js +1 -1
  96. package/dist/src/constants/outputStyles.js +1 -1
  97. package/dist/src/entrypoints/cli.js +1 -1
  98. package/dist/src/hooks/notifs/useCanSwitchToExistingSubscription.js +1 -1
  99. package/dist/src/main.js +1 -1
  100. package/dist/src/projectOnboardingState.js +1 -1
  101. package/dist/src/screens/REPL.js +1 -1
  102. package/dist/src/server/channelServer.js +1 -0
  103. package/dist/src/server/channelServer.test.js +1 -0
  104. package/dist/src/services/api/errors.js +1 -1
  105. package/dist/src/services/api/filesApi.js +1 -1
  106. package/dist/src/services/api/index.js +1 -1
  107. package/dist/src/services/tips/tipRegistry.js +1 -1
  108. package/dist/src/tasks/RemoteAgentTask/RemoteAgentTask.js +1 -1
  109. package/dist/src/tools/AgentTool/AgentTool.js +1 -1
  110. package/dist/src/tools/ConfigTool/supportedSettings.js +1 -1
  111. package/dist/src/tools/EnterWorktreeTool/EnterWorktreeTool.js +1 -1
  112. package/dist/src/tools/ExitWorktreeTool/ExitWorktreeTool.js +1 -1
  113. package/dist/src/tools/WebFetchTool/WebFetchTool.js +1 -1
  114. package/dist/src/utils/computerUse/cleanup.js +1 -1
  115. package/dist/src/utils/computerUse/wrapper.js +1 -1
  116. package/dist/src/utils/fileHistory.js +1 -1
  117. package/dist/src/utils/heartbeat.js +1 -0
  118. package/dist/src/utils/model/providers.js +1 -1
  119. package/dist/src/utils/permissions/filesystem.js +1 -1
  120. package/dist/src/utils/permissions/permissions.js +1 -1
  121. package/dist/src/utils/processUserInput/processSlashCommand.js +1 -1
  122. package/dist/src/utils/releaseNoteTranslations.js +1 -1
  123. package/dist/src/utils/shell/shellToolUtils.js +1 -1
  124. package/dist/src/utils/sideQuery.js +1 -1
  125. package/dist/src/utils/sshMcp/common.js +1 -0
  126. package/dist/src/utils/sshMcp/setup.js +1 -0
  127. package/dist/src/utils/sshMcp/sshMcpServer.js +1 -0
  128. package/dist/src/utils/sshMcp/sshMcpServer.test.js +1 -0
  129. package/dist/src/utils/teleport.js +1 -1
  130. package/dist/src/webapp/server.js +1 -1
  131. package/dist/sshMcpServer.js +1 -0
  132. package/dist/sshMcpServer.test.js +1 -0
  133. package/dist/webapp/chunk-AMCDNAIG.js +1 -0
  134. package/dist/webapp/{chunk-YUEYJPXQ.js → chunk-NFYBHCXF.js} +1 -1
  135. package/dist/webapp/{chunk-DFKSSBHI.js → chunk-OJZNEHPP.js} +1 -1
  136. package/dist/webapp/chunk-VAB2VXFI.js +1 -0
  137. package/dist/webapp/index.html +2 -2
  138. package/dist/webapp/main-MTQLKGXD.js +1 -0
  139. package/dist/webapp/ngsw.json +14 -14
  140. package/dist/webapp/styles-DIKEDJBH.css +1 -0
  141. package/package.json +2 -1
  142. package/dist/webapp/chunk-4SRNXNLW.js +0 -1
  143. package/dist/webapp/chunk-SIHYW6PA.js +0 -1
  144. package/dist/webapp/main-LBNRQBXX.js +0 -1
  145. package/dist/webapp/styles-FUPULZDX.css +0 -1
@@ -1 +1 @@
1
- import{MACRO as e,feature as t}from"../../recovery/bunBundleShim.js";import{randomBytes as o}from"crypto";import r from"ignore";import s from"lodash-es/memoize.js";import{homedir as n,tmpdir as i}from"os";import{join as a,normalize as l,posix as c,sep as u}from"path";import{hasAutoMemPathOverride as d,isAutoMemPath as p}from"../../memdir/paths.js";import{isAgentMemoryPath as h}from"../../tools/AgentTool/agentMemory.js";import{CLAUDE_FOLDER_PERMISSION_PATTERN as f,FILE_EDIT_TOOL_NAME as m,GLOBAL_CLAUDE_FOLDER_PERMISSION_PATTERN as g}from"../../tools/FileEditTool/constants.js";import{getOriginalCwd as w,getSessionId as y}from"../../bootstrap/state.js";import{checkStatsigFeatureGate_CACHED_MAY_BE_STALE as v}from"../../services/analytics/growthbook.js";import{FILE_READ_TOOL_NAME as P}from"../../tools/FileReadTool/prompt.js";import{getCwd as b}from"../cwd.js";import{getClaudeConfigHomeDir as C}from"../envUtils.js";import{getFsImplementation as R,getPathsForPermissionCheck as k}from"../fsOperations.js";import{containsPathTraversal as S,expandPath as W,getDirectoryForPath as I,sanitizePath as x}from"../path.js";import{getPlanSlug as D,getPlansDirectory as F}from"../plans.js";import{getPlatform as j}from"../platform.js";import{getProjectDir as $}from"../sessionStorage.js";import{SETTING_SOURCES as E}from"../settings/constants.js";import{getSettingsFilePathForSource as T,getSettingsRootPathForSource as A}from"../settings/settings.js";import{containsVulnerableUncPath as z}from"../shell/readOnlyCommandValidation.js";import{getToolResultsDir as O}from"../toolResultStorage.js";import{windowsPathToPosixPath as N}from"../windowsPaths.js";import{createReadRuleSuggestion as M}from"./PermissionUpdate.js";import{getRuleByContentsForToolName as q}from"./permissions.js";export const DANGEROUS_FILES=[".gitconfig",".gitmodules",".bashrc",".bash_profile",".zshrc",".zprofile",".profile",".ripgreprc",".mcp.json",".claude.json"];export const DANGEROUS_DIRECTORIES=[".git",".vscode",".idea",".claude"];export function normalizeCaseForComparison(e){return e.toLowerCase()}export function getClaudeSkillScope(e){const t=W(e),o=normalizeCaseForComparison(t),r=[{dir:W(a(w(),".context","skills")),prefix:"/.context/skills/"},{dir:W(a(C(),"skills")),prefix:"~/.context/skills/"},{dir:W(a(w(),".context","skills")),prefix:"/.context/skills/"},{dir:W(a(n(),".context","skills")),prefix:"~/.context/skills/"}];for(const{dir:e,prefix:s}of r){const r=normalizeCaseForComparison(e);for(const n of[u,"/"])if(o.startsWith(r+n.toLowerCase())){const o=t.slice(e.length+n.length),r=o.indexOf("/"),i="\\"===u?o.indexOf("\\"):-1,a=-1===r?i:-1===i?r:Math.min(r,i);if(a<=0)return null;const l=o.slice(0,a);return!l||"."===l||l.includes("..")||/[*?[\]]/.test(l)?null:{skillName:l,pattern:s+l+"/**"}}}return null}const _=c.sep;export function relativePath(e,t){if("windows"===j()){const o=N(e),r=N(t);return c.relative(o,r)}return c.relative(e,t)}export function toPosixPath(e){return"windows"===j()?N(e):e}export function isClaudeSettingsPath(e){const t=normalizeCaseForComparison(W(e));return!(!t.endsWith(`${u}.context${u}settings.json`)&&!t.endsWith(`${u}.context${u}settings.local.json`))||E.map(e=>T(e)).filter(e=>void 0!==e).some(e=>normalizeCaseForComparison(e)===t)}function isClaudeConfigFilePath(e){if(isClaudeSettingsPath(e))return!0;const t=a(w(),".claude","commands"),o=a(w(),".claude","agents"),r=a(w(),".claude","skills"),s=a(w(),".context","commands"),n=a(w(),".context","agents"),i=a(w(),".context","skills");return pathInWorkingPath(e,t)||pathInWorkingPath(e,o)||pathInWorkingPath(e,r)||pathInWorkingPath(e,s)||pathInWorkingPath(e,n)||pathInWorkingPath(e,i)}function isSessionPlanFile(e){const t=a(F(),D()),o=l(e);return o.startsWith(t)&&o.endsWith(".md")}export function getSessionMemoryDir(){return a($(b()),y(),"session-memory")+u}export function getSessionMemoryPath(){return a(getSessionMemoryDir(),"summary.md")}export function isScratchpadEnabled(){return v("tengu_scratch")}export function getClaudeTempDirName(){if("windows"===j())return"claude";return`claude-${process.getuid?.()??0}`}export const getClaudeTempDir=s(function(){const e=process.env.CONTEXT_CODE_TMPDIR||process.env.CLAUDE_CODE_TMPDIR||("windows"===j()?i():"/tmp"),t=R();let o=e;try{o=t.realpathSync(e)}catch{}return a(o,getClaudeTempDirName())+u});export const getBundledSkillsRoot=s(function(){const t=o(16).toString("hex");return a(getClaudeTempDir(),"bundled-skills",e.VERSION,t)});export function getProjectTempDir(){return a(getClaudeTempDir(),x(w()))+u}export function getScratchpadDir(){return a(getProjectTempDir(),y(),"scratchpad")}export async function ensureScratchpadDir(){if(!isScratchpadEnabled())throw new Error("Scratchpad directory feature is not enabled");const e=R(),t=getScratchpadDir();return await e.mkdir(t,{mode:448}),t}function isScratchpadPath(e){if(!isScratchpadEnabled())return!1;const t=getScratchpadDir(),o=l(e);return o===t||o.startsWith(t+u)}function isDangerousFilePathToAutoEdit(e){const t=W(e).split(u),o=t.at(-1);if(e.startsWith("\\\\")||e.startsWith("//"))return!0;for(let e=0;e<t.length;e++){const o=normalizeCaseForComparison(t[e]);for(const r of DANGEROUS_DIRECTORIES)if(o===normalizeCaseForComparison(r)){if(".claude"===r){const o=t[e+1];if(o&&"worktrees"===normalizeCaseForComparison(o))break}return!0}}if(o){const e=normalizeCaseForComparison(o);if(DANGEROUS_FILES.some(t=>normalizeCaseForComparison(t)===e))return!0}return!1}function hasSuspiciousWindowsPathPattern(e){if("windows"===j()||"wsl"===j()){if(-1!==e.indexOf(":",2))return!0}return!!/~\d/.test(e)||(!!(e.startsWith("\\\\?\\")||e.startsWith("\\\\.\\")||e.startsWith("//?/")||e.startsWith("//./"))||(!!/[.\s]+$/.test(e)||(!!/\.(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$/i.test(e)||(!!/(^|\/|\\)\.{3,}(\/|\\|$)/.test(e)||!!z(e)))))}export function checkPathSafetyForAutoEdit(e,t){const o=t??k(e);for(const t of o)if(hasSuspiciousWindowsPathPattern(t))return{safe:!1,message:`Claude requested permissions to write to ${e}, which contains a suspicious Windows path pattern that requires manual approval.`,classifierApprovable:!1};for(const t of o)if(isClaudeConfigFilePath(t))return{safe:!1,message:`Claude requested permissions to write to ${e}, but you haven't granted it yet.`,classifierApprovable:!0};for(const t of o)if(isDangerousFilePathToAutoEdit(t))return{safe:!1,message:`Claude requested permissions to edit ${e} which is a sensitive file.`,classifierApprovable:!0};return{safe:!0}}export function allWorkingDirectories(e){return new Set([w(),...e.additionalWorkingDirectories.keys()])}export const getResolvedWorkingDirPaths=s(k);export function pathInAllowedWorkingPath(e,t,o){const r=o??k(e),s=Array.from(allWorkingDirectories(t)).flatMap(e=>getResolvedWorkingDirPaths(e));return r.every(e=>s.some(t=>pathInWorkingPath(e,t)))}export function pathInWorkingPath(e,t){const o=W(e),r=W(t),s=o.replace(/^\/private\/var\//,"/var/").replace(/^\/private\/tmp(\/|$)/,"/tmp$1"),n=r.replace(/^\/private\/var\//,"/var/").replace(/^\/private\/tmp(\/|$)/,"/tmp$1"),i=normalizeCaseForComparison(s),a=relativePath(normalizeCaseForComparison(n),i);return""===a||!S(a)&&!c.isAbsolute(a)}function rootPathForSource(e){switch(e){case"cliArg":case"command":case"session":return W(w());case"userSettings":case"policySettings":case"projectSettings":case"localSettings":case"flagSettings":return A(e)}}function prependDirSep(e){return c.join(_,e)}function normalizePatternToPath({patternRoot:e,pattern:t,rootPath:o}){const r=c.join(e,t);if(e===o)return prependDirSep(t);if(r.startsWith(`${o}${_}`)){return prependDirSep(r.slice(o.length))}{const r=c.relative(o,e);if(!r||r.startsWith(`..${_}`)||".."===r)return null;return prependDirSep(c.join(r,t))}}export function normalizePatternsToPath(e,t){const o=new Set(e.get(null)??[]);for(const[r,s]of e.entries())if(null!==r)for(const e of s){const s=normalizePatternToPath({patternRoot:r,pattern:e,rootPath:t});s&&o.add(s)}return Array.from(o)}export function getFileReadIgnorePatterns(e){const t=getPatternsByRoot(e,"read","deny"),o=new Map;for(const[e,r]of t.entries())o.set(e,Array.from(r.keys()));return o}function patternWithRoot(e,t){if(e.startsWith(`${_}${_}`)){const t=e.slice(1);if("windows"===j()&&t.match(/^\/[a-z]\//i)){const e=t[1]?.toUpperCase()??"C",o=t.slice(2),r=`${e}:\\`;return{relativePattern:o.startsWith("/")?o.slice(1):o,root:r}}return{relativePattern:t,root:_}}if(e.startsWith(`~${_}`))return{relativePattern:e.slice(1),root:n().normalize("NFC")};if(e.startsWith(_))return{relativePattern:e,root:rootPathForSource(t)};let o=e;return e.startsWith(`.${_}`)&&(o=e.slice(2)),{relativePattern:o,root:null}}function getPatternsByRoot(e,t,o){const r=(()=>{switch(t){case"edit":return m;case"read":return P}})(),s=q(e,r,o),n=new Map;for(const[e,t]of s.entries()){const{relativePattern:o,root:r}=patternWithRoot(e,t.source);let s=n.get(r);void 0===s&&(s=new Map,n.set(r,s)),s.set(o,t)}return n}export function matchingRuleForInput(e,t,o,s){let n=W(e);"windows"===j()&&n.includes("\\")&&(n=N(n));const i=getPatternsByRoot(t,o,s);for(const[e,t]of i.entries()){const o=Array.from(t.keys()).map(e=>{let t=e;return t.endsWith("/**")&&(t=t.slice(0,-3)),t}),s=r().add(o),i=relativePath(e??b(),n??b());if(i.startsWith(`..${_}`))continue;if(!i)continue;const a=s.test(i);if(a.ignored&&a.rule){const e=a.rule.pattern,o=e+"/**";return t.has(o)?t.get(o)??null:t.get(e)??null}}return null}export function checkReadPermissionForTool(e,t,o){if("function"!=typeof e.getPath)return{behavior:"ask",message:`Claude requested permissions to use ${e.name}, but you haven't granted it yet.`};const r=e.getPath(t),s=k(r);for(const e of s)if(e.startsWith("\\\\")||e.startsWith("//"))return{behavior:"ask",message:`Claude requested permissions to read from ${r}, which appears to be a UNC path that could access network resources.`,decisionReason:{type:"other",reason:"UNC path detected (defense-in-depth check)"}};for(const e of s)if(hasSuspiciousWindowsPathPattern(e))return{behavior:"ask",message:`Claude requested permissions to read from ${r}, which contains a suspicious Windows path pattern that requires manual approval.`,decisionReason:{type:"other",reason:"Path contains suspicious Windows-specific patterns (alternate data streams, short names, long path prefixes, or three or more consecutive dots) that require manual verification"}};for(const e of s){const t=matchingRuleForInput(e,o,"read","deny");if(t)return{behavior:"deny",message:`Permission to read ${r} has been denied.`,decisionReason:{type:"rule",rule:t}}}for(const e of s){const t=matchingRuleForInput(e,o,"read","ask");if(t)return{behavior:"ask",message:`Claude requested permissions to read from ${r}, but you haven't granted it yet.`,decisionReason:{type:"rule",rule:t}}}const n=checkWritePermissionForTool(e,t,o,s);if("allow"===n.behavior)return n;if(pathInAllowedWorkingPath(r,o,s))return{behavior:"allow",updatedInput:t,decisionReason:{type:"mode",mode:"default"}};const i=checkReadableInternalPath(W(r),t);if("passthrough"!==i.behavior)return i;const a=matchingRuleForInput(r,o,"read","allow");return a?{behavior:"allow",updatedInput:t,decisionReason:{type:"rule",rule:a}}:{behavior:"ask",message:`Claude requested permissions to read from ${r}, but you haven't granted it yet.`,suggestions:generateSuggestions(r,"read",o,s),decisionReason:{type:"workingDir",reason:"Path is outside allowed working directories"}}}export function checkWritePermissionForTool(e,t,o,r){if("function"!=typeof e.getPath)return{behavior:"ask",message:`Claude requested permissions to use ${e.name}, but you haven't granted it yet.`};const s=e.getPath(t),n=r??k(s);for(const e of n){const t=matchingRuleForInput(e,o,"edit","deny");if(t)return{behavior:"deny",message:`Permission to edit ${s} has been denied.`,decisionReason:{type:"rule",rule:t}}}const i=checkEditableInternalPath(W(s),t);if("passthrough"!==i.behavior)return i;const a=matchingRuleForInput(s,{...o,alwaysAllowRules:{session:o.alwaysAllowRules.session??[]}},"edit","allow");if(a){const e=a.ruleValue.ruleContent;if(e&&(e.startsWith(f.slice(0,-2))||e.startsWith(g.slice(0,-2)))&&!e.includes("..")&&e.endsWith("/**"))return{behavior:"allow",updatedInput:t,decisionReason:{type:"rule",rule:a}}}const l=checkPathSafetyForAutoEdit(s,n);if(!l.safe){const e=getClaudeSkillScope(s),t=e?[{type:"addRules",rules:[{toolName:m,ruleContent:e.pattern}],behavior:"allow",destination:"session"}]:generateSuggestions(s,"write",o,n);return{behavior:"ask",message:l.message,suggestions:t,decisionReason:{type:"safetyCheck",reason:l.message,classifierApprovable:l.classifierApprovable}}}for(const e of n){const t=matchingRuleForInput(e,o,"edit","ask");if(t)return{behavior:"ask",message:`Claude requested permissions to write to ${s}, but you haven't granted it yet.`,decisionReason:{type:"rule",rule:t}}}const c=pathInAllowedWorkingPath(s,o,n);if("acceptEdits"===o.mode&&c)return{behavior:"allow",updatedInput:t,decisionReason:{type:"mode",mode:o.mode}};const u=matchingRuleForInput(s,o,"edit","allow");return u?{behavior:"allow",updatedInput:t,decisionReason:{type:"rule",rule:u}}:{behavior:"ask",message:`Claude requested permissions to write to ${s}, but you haven't granted it yet.`,suggestions:generateSuggestions(s,"write",o,n),decisionReason:c?void 0:{type:"workingDir",reason:"Path is outside allowed working directories"}}}export function generateSuggestions(e,t,o,r){const s=!pathInAllowedWorkingPath(e,o,r);if("read"===t&&s){const t=I(e);return k(t).map(e=>M(e,"session")).filter(e=>void 0!==e)}const n="default"===o.mode||"plan"===o.mode;if("write"===t||"create"===t){const t=n?[{type:"setMode",mode:"acceptEdits",destination:"session"}]:[];if(s){const o=I(e),r=k(o);t.push({type:"addDirectories",directories:r,destination:"session"})}return t}return n?[{type:"setMode",mode:"acceptEdits",destination:"session"}]:[]}export function checkEditableInternalPath(e,o){const r=l(e);if(isSessionPlanFile(r))return{behavior:"allow",updatedInput:o,decisionReason:{type:"other",reason:"Plan files for current session are allowed for writing"}};if(isScratchpadPath(r))return{behavior:"allow",updatedInput:o,decisionReason:{type:"other",reason:"Scratchpad files for current session are allowed for writing"}};if(t("TEMPLATES")){const t=process.env.CONTEXT_JOB_DIR??process.env.CLAUDE_JOB_DIR;if(t){const r=a(C(),"jobs"),s=k(t).map(l),n=k(r).map(l);if(s.every(e=>n.some(t=>e.startsWith(t+u)))){if(k(e).every(e=>{const t=l(e);return s.some(e=>t===e||t.startsWith(e+u))}))return{behavior:"allow",updatedInput:o,decisionReason:{type:"other",reason:"Job directory files for current job are allowed for writing"}}}}}return h(r)?{behavior:"allow",updatedInput:o,decisionReason:{type:"other",reason:"Agent memory files are allowed for writing"}}:!d()&&p(r)?{behavior:"allow",updatedInput:o,decisionReason:{type:"other",reason:"auto memory files are allowed for writing"}}:normalizeCaseForComparison(r)===normalizeCaseForComparison(a(w(),".claude","launch.json"))?{behavior:"allow",updatedInput:o,decisionReason:{type:"other",reason:"Preview launch config is allowed for writing"}}:{behavior:"passthrough",message:""}}export function checkReadableInternalPath(e,t){const o=l(e);if(function(e){return l(e).startsWith(getSessionMemoryDir())}(o))return{behavior:"allow",updatedInput:t,decisionReason:{type:"other",reason:"Session memory files are allowed for reading"}};if(function(e){const t=$(b()),o=l(e);return o===t||o.startsWith(t+u)}(o))return{behavior:"allow",updatedInput:t,decisionReason:{type:"other",reason:"Project directory files are allowed for reading"}};if(isSessionPlanFile(o))return{behavior:"allow",updatedInput:t,decisionReason:{type:"other",reason:"Plan files for current session are allowed for reading"}};const r=O(),s=r.endsWith(u)?r:r+u;if(o===r||o.startsWith(s))return{behavior:"allow",updatedInput:t,decisionReason:{type:"other",reason:"Tool result files are allowed for reading"}};if(isScratchpadPath(o))return{behavior:"allow",updatedInput:t,decisionReason:{type:"other",reason:"Scratchpad files for current session are allowed for reading"}};const n=getProjectTempDir();if(o.startsWith(n))return{behavior:"allow",updatedInput:t,decisionReason:{type:"other",reason:"Project temp directory files are allowed for reading"}};if(h(o))return{behavior:"allow",updatedInput:t,decisionReason:{type:"other",reason:"Agent memory files are allowed for reading"}};if(p(o))return{behavior:"allow",updatedInput:t,decisionReason:{type:"other",reason:"auto memory files are allowed for reading"}};const i=a(C(),"tasks")+u;if(o===i.slice(0,-1)||o.startsWith(i))return{behavior:"allow",updatedInput:t,decisionReason:{type:"other",reason:"Task files are allowed for reading"}};const c=a(C(),"teams")+u;if(o===c.slice(0,-1)||o.startsWith(c))return{behavior:"allow",updatedInput:t,decisionReason:{type:"other",reason:"Team files are allowed for reading"}};const d=getBundledSkillsRoot()+u;return o.startsWith(d)?{behavior:"allow",updatedInput:t,decisionReason:{type:"other",reason:"Bundled skill reference files are allowed for reading"}}:{behavior:"passthrough",message:""}}
1
+ import{MACRO as e,feature as t}from"../../recovery/bunBundleShim.js";import{randomBytes as o}from"crypto";import r from"ignore";import s from"lodash-es/memoize.js";import{homedir as n,tmpdir as i}from"os";import{join as a,normalize as l,posix as c,sep as u}from"path";import{hasAutoMemPathOverride as p,isAutoMemPath as d}from"../../memdir/paths.js";import{isAgentMemoryPath as h}from"../../tools/AgentTool/agentMemory.js";import{CLAUDE_FOLDER_PERMISSION_PATTERN as f,FILE_EDIT_TOOL_NAME as m,GLOBAL_CLAUDE_FOLDER_PERMISSION_PATTERN as g}from"../../tools/FileEditTool/constants.js";import{getOriginalCwd as w,getSessionId as P}from"../../bootstrap/state.js";import{checkStatsigFeatureGate_CACHED_MAY_BE_STALE as v}from"../../services/analytics/growthbook.js";import{FILE_READ_TOOL_NAME as y}from"../../tools/FileReadTool/prompt.js";import{getCwd as C}from"../cwd.js";import{getClaudeConfigHomeDir as R}from"../envUtils.js";import{getFsImplementation as b,getPathsForPermissionCheck as k}from"../fsOperations.js";import{containsPathTraversal as S,expandPath as W,getDirectoryForPath as I,sanitizePath as x}from"../path.js";import{getPlanSlug as D,getPlansDirectory as F}from"../plans.js";import{getPlatform as j}from"../platform.js";import{getProjectDir as $}from"../sessionStorage.js";import{SETTING_SOURCES as E}from"../settings/constants.js";import{getSettingsFilePathForSource as T,getSettingsRootPathForSource as A}from"../settings/settings.js";import{containsVulnerableUncPath as z}from"../shell/readOnlyCommandValidation.js";import{getToolResultsDir as O}from"../toolResultStorage.js";import{windowsPathToPosixPath as N}from"../windowsPaths.js";import{createReadRuleSuggestion as M}from"./PermissionUpdate.js";import{getRuleByContentsForToolName as _}from"./permissions.js";export const DANGEROUS_FILES=[".gitconfig",".gitmodules",".bashrc",".bash_profile",".zshrc",".zprofile",".profile",".ripgreprc",".mcp.json",".claude.json"];export const DANGEROUS_DIRECTORIES=[".git",".vscode",".idea",".claude"];export function normalizeCaseForComparison(e){return e.toLowerCase()}export function getClaudeSkillScope(e){const t=W(e),o=normalizeCaseForComparison(t),r=[{dir:W(a(w(),".context","skills")),prefix:"/.context/skills/"},{dir:W(a(R(),"skills")),prefix:"~/.context/skills/"},{dir:W(a(w(),".context","skills")),prefix:"/.context/skills/"},{dir:W(a(n(),".context","skills")),prefix:"~/.context/skills/"}];for(const{dir:e,prefix:s}of r){const r=normalizeCaseForComparison(e);for(const n of[u,"/"])if(o.startsWith(r+n.toLowerCase())){const o=t.slice(e.length+n.length),r=o.indexOf("/"),i="\\"===u?o.indexOf("\\"):-1,a=-1===r?i:-1===i?r:Math.min(r,i);if(a<=0)return null;const l=o.slice(0,a);return!l||"."===l||l.includes("..")||/[*?[\]]/.test(l)?null:{skillName:l,pattern:s+l+"/**"}}}return null}const U=c.sep;export function relativePath(e,t){if("windows"===j()){const o=N(e),r=N(t);return c.relative(o,r)}return c.relative(e,t)}export function toPosixPath(e){return"windows"===j()?N(e):e}export function isClaudeSettingsPath(e){const t=normalizeCaseForComparison(W(e));return!(!t.endsWith(`${u}.context${u}settings.json`)&&!t.endsWith(`${u}.context${u}settings.local.json`))||E.map(e=>T(e)).filter(e=>void 0!==e).some(e=>normalizeCaseForComparison(e)===t)}function isClaudeConfigFilePath(e){if(isClaudeSettingsPath(e))return!0;const t=a(w(),".claude","commands"),o=a(w(),".claude","agents"),r=a(w(),".claude","skills"),s=a(w(),".context","commands"),n=a(w(),".context","agents"),i=a(w(),".context","skills");return pathInWorkingPath(e,t)||pathInWorkingPath(e,o)||pathInWorkingPath(e,r)||pathInWorkingPath(e,s)||pathInWorkingPath(e,n)||pathInWorkingPath(e,i)}function isSessionPlanFile(e){const t=a(F(),D()),o=l(e);return o.startsWith(t)&&o.endsWith(".md")}export function getSessionMemoryDir(){return a($(C()),P(),"session-memory")+u}export function getSessionMemoryPath(){return a(getSessionMemoryDir(),"summary.md")}export function isScratchpadEnabled(){return v("tengu_scratch")}export function getClaudeTempDirName(){if("windows"===j())return"claude";return`claude-${process.getuid?.()??0}`}export const getClaudeTempDir=s(function(){const e=process.env.CONTEXT_CODE_TMPDIR||process.env.CLAUDE_CODE_TMPDIR||("windows"===j()?i():"/tmp"),t=b();let o=e;try{o=t.realpathSync(e)}catch{}return a(o,getClaudeTempDirName())+u});export const getBundledSkillsRoot=s(function(){const t=o(16).toString("hex");return a(getClaudeTempDir(),"bundled-skills",e.VERSION,t)});export function getProjectTempDir(){return a(getClaudeTempDir(),x(w()))+u}export function getScratchpadDir(){return a(getProjectTempDir(),P(),"scratchpad")}export async function ensureScratchpadDir(){if(!isScratchpadEnabled())throw new Error("Scratchpad directory feature is not enabled");const e=b(),t=getScratchpadDir();return await e.mkdir(t,{mode:448}),t}function isScratchpadPath(e){if(!isScratchpadEnabled())return!1;const t=getScratchpadDir(),o=l(e);return o===t||o.startsWith(t+u)}function isDangerousFilePathToAutoEdit(e){const t=W(e).split(u),o=t.at(-1);if(e.startsWith("\\\\")||e.startsWith("//"))return!0;for(let e=0;e<t.length;e++){const o=normalizeCaseForComparison(t[e]);for(const r of DANGEROUS_DIRECTORIES)if(o===normalizeCaseForComparison(r)){if(".claude"===r){const o=t[e+1];if(o&&"worktrees"===normalizeCaseForComparison(o))break}return!0}}if(o){const e=normalizeCaseForComparison(o);if(DANGEROUS_FILES.some(t=>normalizeCaseForComparison(t)===e))return!0}return!1}function hasSuspiciousWindowsPathPattern(e){if("windows"===j()||"wsl"===j()){if(-1!==e.indexOf(":",2))return!0}return!!/~\d/.test(e)||(!!(e.startsWith("\\\\?\\")||e.startsWith("\\\\.\\")||e.startsWith("//?/")||e.startsWith("//./"))||(!!/[.\s]+$/.test(e)||(!!/\.(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$/i.test(e)||(!!/(^|\/|\\)\.{3,}(\/|\\|$)/.test(e)||!!z(e)))))}export function checkPathSafetyForAutoEdit(e,t){const o=t??k(e);for(const t of o)if(hasSuspiciousWindowsPathPattern(t))return{safe:!1,message:`Claude requested permissions to write to ${e}, which contains a suspicious Windows path pattern that requires manual approval.`,classifierApprovable:!1};for(const t of o)if(isClaudeConfigFilePath(t))return{safe:!1,message:`Context solicitó permisos para escribir en ${e}, pero aún no se los has otorgado.`,classifierApprovable:!0};for(const t of o)if(isDangerousFilePathToAutoEdit(t))return{safe:!1,message:`Claude requested permissions to edit ${e} which is a sensitive file.`,classifierApprovable:!0};return{safe:!0}}export function allWorkingDirectories(e){return new Set([w(),...e.additionalWorkingDirectories.keys()])}export const getResolvedWorkingDirPaths=s(k);export function pathInAllowedWorkingPath(e,t,o){const r=o??k(e),s=Array.from(allWorkingDirectories(t)).flatMap(e=>getResolvedWorkingDirPaths(e));return r.every(e=>s.some(t=>pathInWorkingPath(e,t)))}export function pathInWorkingPath(e,t){const o=W(e),r=W(t),s=o.replace(/^\/private\/var\//,"/var/").replace(/^\/private\/tmp(\/|$)/,"/tmp$1"),n=r.replace(/^\/private\/var\//,"/var/").replace(/^\/private\/tmp(\/|$)/,"/tmp$1"),i=normalizeCaseForComparison(s),a=relativePath(normalizeCaseForComparison(n),i);return""===a||!S(a)&&!c.isAbsolute(a)}function rootPathForSource(e){switch(e){case"cliArg":case"command":case"session":return W(w());case"userSettings":case"policySettings":case"projectSettings":case"localSettings":case"flagSettings":return A(e)}}function prependDirSep(e){return c.join(U,e)}function normalizePatternToPath({patternRoot:e,pattern:t,rootPath:o}){const r=c.join(e,t);if(e===o)return prependDirSep(t);if(r.startsWith(`${o}${U}`)){return prependDirSep(r.slice(o.length))}{const r=c.relative(o,e);if(!r||r.startsWith(`..${U}`)||".."===r)return null;return prependDirSep(c.join(r,t))}}export function normalizePatternsToPath(e,t){const o=new Set(e.get(null)??[]);for(const[r,s]of e.entries())if(null!==r)for(const e of s){const s=normalizePatternToPath({patternRoot:r,pattern:e,rootPath:t});s&&o.add(s)}return Array.from(o)}export function getFileReadIgnorePatterns(e){const t=getPatternsByRoot(e,"read","deny"),o=new Map;for(const[e,r]of t.entries())o.set(e,Array.from(r.keys()));return o}function patternWithRoot(e,t){if(e.startsWith(`${U}${U}`)){const t=e.slice(1);if("windows"===j()&&t.match(/^\/[a-z]\//i)){const e=t[1]?.toUpperCase()??"C",o=t.slice(2),r=`${e}:\\`;return{relativePattern:o.startsWith("/")?o.slice(1):o,root:r}}return{relativePattern:t,root:U}}if(e.startsWith(`~${U}`))return{relativePattern:e.slice(1),root:n().normalize("NFC")};if(e.startsWith(U))return{relativePattern:e,root:rootPathForSource(t)};let o=e;return e.startsWith(`.${U}`)&&(o=e.slice(2)),{relativePattern:o,root:null}}function getPatternsByRoot(e,t,o){const r=(()=>{switch(t){case"edit":return m;case"read":return y}})(),s=_(e,r,o),n=new Map;for(const[e,t]of s.entries()){const{relativePattern:o,root:r}=patternWithRoot(e,t.source);let s=n.get(r);void 0===s&&(s=new Map,n.set(r,s)),s.set(o,t)}return n}export function matchingRuleForInput(e,t,o,s){let n=W(e);"windows"===j()&&n.includes("\\")&&(n=N(n));const i=getPatternsByRoot(t,o,s);for(const[e,t]of i.entries()){const o=Array.from(t.keys()).map(e=>{let t=e;return t.endsWith("/**")&&(t=t.slice(0,-3)),t}),s=r().add(o),i=relativePath(e??C(),n??C());if(i.startsWith(`..${U}`))continue;if(!i)continue;const a=s.test(i);if(a.ignored&&a.rule){const e=a.rule.pattern,o=e+"/**";return t.has(o)?t.get(o)??null:t.get(e)??null}}return null}export function checkReadPermissionForTool(e,t,o){if("function"!=typeof e.getPath)return{behavior:"ask",message:`Claude requested permissions to use ${e.name}, but you haven't granted it yet.`};const r=e.getPath(t),s=k(r);for(const e of s)if(e.startsWith("\\\\")||e.startsWith("//"))return{behavior:"ask",message:`Claude requested permissions to read from ${r}, which appears to be a UNC path that could access network resources.`,decisionReason:{type:"other",reason:"UNC path detected (defense-in-depth check)"}};for(const e of s)if(hasSuspiciousWindowsPathPattern(e))return{behavior:"ask",message:`Claude requested permissions to read from ${r}, which contains a suspicious Windows path pattern that requires manual approval.`,decisionReason:{type:"other",reason:"Path contains suspicious Windows-specific patterns (alternate data streams, short names, long path prefixes, or three or more consecutive dots) that require manual verification"}};for(const e of s){const t=matchingRuleForInput(e,o,"read","deny");if(t)return{behavior:"deny",message:`Permission to read ${r} has been denied.`,decisionReason:{type:"rule",rule:t}}}for(const e of s){const t=matchingRuleForInput(e,o,"read","ask");if(t)return{behavior:"ask",message:`Context solicitó permisos para leer desde ${r}, pero aún no se los has otorgado.`,decisionReason:{type:"rule",rule:t}}}const n=checkWritePermissionForTool(e,t,o,s);if("allow"===n.behavior)return n;if(pathInAllowedWorkingPath(r,o,s))return{behavior:"allow",updatedInput:t,decisionReason:{type:"mode",mode:"default"}};const i=checkReadableInternalPath(W(r),t);if("passthrough"!==i.behavior)return i;const a=matchingRuleForInput(r,o,"read","allow");return a?{behavior:"allow",updatedInput:t,decisionReason:{type:"rule",rule:a}}:{behavior:"ask",message:`Context solicitó permisos para leer desde ${r}, pero aún no se los has otorgado.`,suggestions:generateSuggestions(r,"read",o,s),decisionReason:{type:"workingDir",reason:"Path is outside allowed working directories"}}}export function checkWritePermissionForTool(e,t,o,r){if("function"!=typeof e.getPath)return{behavior:"ask",message:`Claude requested permissions to use ${e.name}, but you haven't granted it yet.`};const s=e.getPath(t),n=r??k(s);for(const e of n){const t=matchingRuleForInput(e,o,"edit","deny");if(t)return{behavior:"deny",message:`Permission to edit ${s} has been denied.`,decisionReason:{type:"rule",rule:t}}}const i=checkEditableInternalPath(W(s),t);if("passthrough"!==i.behavior)return i;const a=matchingRuleForInput(s,{...o,alwaysAllowRules:{session:o.alwaysAllowRules.session??[]}},"edit","allow");if(a){const e=a.ruleValue.ruleContent;if(e&&(e.startsWith(f.slice(0,-2))||e.startsWith(g.slice(0,-2)))&&!e.includes("..")&&e.endsWith("/**"))return{behavior:"allow",updatedInput:t,decisionReason:{type:"rule",rule:a}}}const l=checkPathSafetyForAutoEdit(s,n);if(!l.safe){const e=getClaudeSkillScope(s),t=e?[{type:"addRules",rules:[{toolName:m,ruleContent:e.pattern}],behavior:"allow",destination:"session"}]:generateSuggestions(s,"write",o,n);return{behavior:"ask",message:l.message,suggestions:t,decisionReason:{type:"safetyCheck",reason:l.message,classifierApprovable:l.classifierApprovable}}}for(const e of n){const t=matchingRuleForInput(e,o,"edit","ask");if(t)return{behavior:"ask",message:`Context solicitó permisos para escribir en ${s}, pero aún no se los has otorgado.`,decisionReason:{type:"rule",rule:t}}}const c=pathInAllowedWorkingPath(s,o,n);if("acceptEdits"===o.mode&&c)return{behavior:"allow",updatedInput:t,decisionReason:{type:"mode",mode:o.mode}};const u=matchingRuleForInput(s,o,"edit","allow");return u?{behavior:"allow",updatedInput:t,decisionReason:{type:"rule",rule:u}}:{behavior:"ask",message:`Context solicitó permisos para escribir en ${s}, pero aún no se los has otorgado.`,suggestions:generateSuggestions(s,"write",o,n),decisionReason:c?void 0:{type:"workingDir",reason:"Path is outside allowed working directories"}}}export function generateSuggestions(e,t,o,r){const s=!pathInAllowedWorkingPath(e,o,r);if("read"===t&&s){const t=I(e);return k(t).map(e=>M(e,"session")).filter(e=>void 0!==e)}const n="default"===o.mode||"plan"===o.mode;if("write"===t||"create"===t){const t=n?[{type:"setMode",mode:"acceptEdits",destination:"session"}]:[];if(s){const o=I(e),r=k(o);t.push({type:"addDirectories",directories:r,destination:"session"})}return t}return n?[{type:"setMode",mode:"acceptEdits",destination:"session"}]:[]}export function checkEditableInternalPath(e,o){const r=l(e);if(isSessionPlanFile(r))return{behavior:"allow",updatedInput:o,decisionReason:{type:"other",reason:"Plan files for current session are allowed for writing"}};if(isScratchpadPath(r))return{behavior:"allow",updatedInput:o,decisionReason:{type:"other",reason:"Scratchpad files for current session are allowed for writing"}};if(t("TEMPLATES")){const t=process.env.CONTEXT_JOB_DIR??process.env.CLAUDE_JOB_DIR;if(t){const r=a(R(),"jobs"),s=k(t).map(l),n=k(r).map(l);if(s.every(e=>n.some(t=>e.startsWith(t+u)))){if(k(e).every(e=>{const t=l(e);return s.some(e=>t===e||t.startsWith(e+u))}))return{behavior:"allow",updatedInput:o,decisionReason:{type:"other",reason:"Job directory files for current job are allowed for writing"}}}}}return h(r)?{behavior:"allow",updatedInput:o,decisionReason:{type:"other",reason:"Agent memory files are allowed for writing"}}:!p()&&d(r)?{behavior:"allow",updatedInput:o,decisionReason:{type:"other",reason:"auto memory files are allowed for writing"}}:normalizeCaseForComparison(r)===normalizeCaseForComparison(a(w(),".claude","launch.json"))?{behavior:"allow",updatedInput:o,decisionReason:{type:"other",reason:"Preview launch config is allowed for writing"}}:{behavior:"passthrough",message:""}}export function checkReadableInternalPath(e,t){const o=l(e);if(function(e){return l(e).startsWith(getSessionMemoryDir())}(o))return{behavior:"allow",updatedInput:t,decisionReason:{type:"other",reason:"Session memory files are allowed for reading"}};if(function(e){const t=$(C()),o=l(e);return o===t||o.startsWith(t+u)}(o))return{behavior:"allow",updatedInput:t,decisionReason:{type:"other",reason:"Project directory files are allowed for reading"}};if(isSessionPlanFile(o))return{behavior:"allow",updatedInput:t,decisionReason:{type:"other",reason:"Plan files for current session are allowed for reading"}};const r=O(),s=r.endsWith(u)?r:r+u;if(o===r||o.startsWith(s))return{behavior:"allow",updatedInput:t,decisionReason:{type:"other",reason:"Tool result files are allowed for reading"}};if(isScratchpadPath(o))return{behavior:"allow",updatedInput:t,decisionReason:{type:"other",reason:"Scratchpad files for current session are allowed for reading"}};const n=getProjectTempDir();if(o.startsWith(n))return{behavior:"allow",updatedInput:t,decisionReason:{type:"other",reason:"Project temp directory files are allowed for reading"}};if(h(o))return{behavior:"allow",updatedInput:t,decisionReason:{type:"other",reason:"Agent memory files are allowed for reading"}};if(d(o))return{behavior:"allow",updatedInput:t,decisionReason:{type:"other",reason:"auto memory files are allowed for reading"}};const i=a(R(),"tasks")+u;if(o===i.slice(0,-1)||o.startsWith(i))return{behavior:"allow",updatedInput:t,decisionReason:{type:"other",reason:"Task files are allowed for reading"}};const c=a(R(),"teams")+u;if(o===c.slice(0,-1)||o.startsWith(c))return{behavior:"allow",updatedInput:t,decisionReason:{type:"other",reason:"Team files are allowed for reading"}};const p=getBundledSkillsRoot()+u;return o.startsWith(p)?{behavior:"allow",updatedInput:t,decisionReason:{type:"other",reason:"Bundled skill reference files are allowed for reading"}}:{behavior:"passthrough",message:""}}
@@ -1 +1 @@
1
- import{feature as e}from"../../recovery/bunBundleShim.js";import{createRequire as o}from"module";const s=o(import.meta.url);import{APIUserAbortError as t}from"@anthropic-ai/sdk/index.js";import{getToolNameForPermissionCheck as i,mcpInfoFromString as n}from"../../services/mcp/mcpStringUtils.js";import{AGENT_TOOL_NAME as a}from"../../tools/AgentTool/constants.js";import{shouldUseSandbox as r}from"../../tools/BashTool/shouldUseSandbox.js";import{BASH_TOOL_NAME as l}from"../../tools/BashTool/toolName.js";import{POWERSHELL_TOOL_NAME as u}from"../../tools/PowerShellTool/toolName.js";import{REPL_TOOL_NAME as c}from"../../tools/REPLTool/constants.js";import{extractOutputRedirections as m}from"../bash/commands.js";import{logForDebugging as d}from"../debug.js";import{AbortError as p,toError as f}from"../errors.js";import{logError as g}from"../log.js";import{SandboxManager as h}from"../sandbox/sandbox-adapter.js";import{getSettingSourceDisplayNameLowercase as v,SETTING_SOURCES as R}from"../settings/constants.js";import{plural as k}from"../stringUtils.js";import{permissionModeTitle as y}from"./PermissionMode.js";import{applyPermissionUpdate as b,applyPermissionUpdates as P,persistPermissionUpdates as S}from"./PermissionUpdate.js";import{permissionRuleValueFromString as w,permissionRuleValueToString as C}from"./permissionRuleParser.js";import{deletePermissionRuleFromSettings as T,shouldAllowManagedPermissionRulesOnly as A}from"./permissionsLoader.js";const x=e("TRANSCRIPT_CLASSIFIER")?s("./classifierDecision.js"):null,I=e("TRANSCRIPT_CLASSIFIER")?s("./autoModeState.js"):null;import{addToTurnClassifierDuration as D,getTotalCacheCreationInputTokens as $,getTotalCacheReadInputTokens as j,getTotalInputTokens as M,getTotalOutputTokens as N}from"../../bootstrap/state.js";import{getFeatureValue_CACHED_WITH_REFRESH as U}from"../../services/analytics/growthbook.js";import{logEvent as q}from"../../services/analytics/index.js";import{sanitizeToolNameForAnalytics as B}from"../../services/analytics/metadata.js";import{clearClassifierChecking as _,setClassifierChecking as E}from"../classifierApprovals.js";import{isInProtectedNamespace as F}from"../envUtils.js";import{executePermissionRequestHooks as L}from"../hooks.js";import{AUTO_REJECT_MESSAGE as V,buildClassifierUnavailableMessage as O,buildYoloRejectionMessage as H,DONT_ASK_REJECT_MESSAGE as Q}from"../messages.js";import{calculateCostFromTokens as W}from"../modelCost.js";import{jsonStringify as Y}from"../slowOperations.js";import{createDenialTrackingState as z,DENIAL_LIMITS as G,recordDenial as J,recordSuccess as K,shouldFallbackToPrompting as X}from"./denialTracking.js";import{classifyYoloAction as Z,formatActionForClassifier as ee}from"./yoloClassifier.js";import{findPermissionRule as oe}from"./permissionsDb.js";const se=[...R,"cliArg","command","session"];export function permissionRuleSourceDisplayString(e){return v(e)}export function getAllowRules(e){return se.flatMap(o=>(e.alwaysAllowRules[o]||[]).map(e=>({source:o,ruleBehavior:"allow",ruleValue:w(e)})))}export function createPermissionRequestMessage(o,s){if(s){if((e("BASH_CLASSIFIER")||e("TRANSCRIPT_CLASSIFIER"))&&"classifier"===s.type)return`Classifier '${s.classifier}' requires approval for this ${o} command: ${s.reason}`;switch(s.type){case"hook":return s.reason?`Hook '${s.hookName}' blocked this action: ${s.reason}`:`Hook '${s.hookName}' requires approval for this ${o} command`;case"rule":return`Permission rule '${C(s.rule.ruleValue)}' from ${permissionRuleSourceDisplayString(s.rule.source)} requires approval for this ${o} command`;case"subcommandResults":{const e=[];for(const[t,i]of s.reasons)if("ask"===i.behavior||"passthrough"===i.behavior)if("Bash"===o){const{commandWithoutRedirections:o,redirections:s}=m(t),i=s.length>0?o:t;e.push(i)}else e.push(t);if(e.length>0){const s=e.length;return`This ${o} command contains multiple operations. The following ${k(s,"part")} ${k(s,"requires","require")} approval: ${e.join(", ")}`}return`This ${o} command contains multiple operations that require approval`}case"permissionPromptTool":return`Tool '${s.permissionPromptToolName}' requires approval for this ${o} command`;case"sandboxOverride":return"Run outside of the sandbox";case"workingDir":case"safetyCheck":case"other":case"asyncAgent":return s.reason;case"mode":return`Current permission mode (${y(s.mode)}) requires approval for this ${o} command`}}return`Claude requested permissions to use ${o}, but you haven't granted it yet.`}export function getDenyRules(e){return se.flatMap(o=>(e.alwaysDenyRules[o]||[]).map(e=>({source:o,ruleBehavior:"deny",ruleValue:w(e)})))}export function getAskRules(e){return se.flatMap(o=>(e.alwaysAskRules[o]||[]).map(e=>({source:o,ruleBehavior:"ask",ruleValue:w(e)})))}function toolMatchesRule(e,o){if(void 0!==o.ruleValue.ruleContent)return!1;const s=i(e);if(o.ruleValue.toolName===s)return!0;const t=n(o.ruleValue.toolName),a=n(s);return null!==t&&null!==a&&(void 0===t.toolName||"*"===t.toolName)&&t.serverName===a.serverName}export function toolAlwaysAllowedRule(e,o){return getAllowRules(e).find(e=>toolMatchesRule(o,e))||null}export function getDenyRuleForTool(e,o){return getDenyRules(e).find(e=>toolMatchesRule(o,e))||null}export function getAskRuleForTool(e,o){return getAskRules(e).find(e=>toolMatchesRule(o,e))||null}export function getDenyRuleForAgent(e,o,s){return getDenyRules(e).find(e=>e.ruleValue.toolName===o&&e.ruleValue.ruleContent===s)||null}export function filterDeniedAgents(e,o,s){const t=new Set;for(const e of getDenyRules(o))e.ruleValue.toolName===s&&void 0!==e.ruleValue.ruleContent&&t.add(e.ruleValue.ruleContent);return e.filter(e=>!t.has(e.agentType))}export function getRuleByContentsForTool(e,o,s){return getRuleByContentsForToolName(e,i(o),s)}export function getRuleByContentsForToolName(e,o,s){const t=new Map;let i=[];switch(s){case"allow":i=getAllowRules(e);break;case"deny":i=getDenyRules(e);break;case"ask":i=getAskRules(e)}for(const e of i)e.ruleValue.toolName===o&&void 0!==e.ruleValue.ruleContent&&e.ruleBehavior===s&&t.set(e.ruleValue.ruleContent,e);return t}export const hasPermissionsToUseTool=async(o,s,n,m,v)=>{const R=await async function(e,o,s){if(s.abortController.signal.aborted)throw new p;let n=s.getAppState();const a=getDenyRuleForTool(n.toolPermissionContext,e);if(a)return{behavior:"deny",decisionReason:{type:"rule",rule:a},message:`Permission to use ${e.name} has been denied.`};const u=getAskRuleForTool(n.toolPermissionContext,e);if(u){if(!(e.name===l&&h.isSandboxingEnabled()&&h.isAutoAllowBashIfSandboxedEnabled()&&r(o)))return{behavior:"ask",decisionReason:{type:"rule",rule:u},message:createPermissionRequestMessage(e.name)}}let c={behavior:"passthrough",message:createPermissionRequestMessage(e.name)};try{const t=e.inputSchema.parse(o);c=await e.checkPermissions(t,s)}catch(e){if(e instanceof p||e instanceof t)throw e;g(e)}if("deny"===c?.behavior)return c;if(e.requiresUserInteraction?.()&&"ask"===c?.behavior)return c;if("ask"===c?.behavior&&"rule"===c.decisionReason?.type&&"ask"===c.decisionReason.rule.ruleBehavior)return c;if("ask"===c?.behavior&&"safetyCheck"===c.decisionReason?.type)return c;n=s.getAppState();if("bypassPermissions"===n.toolPermissionContext.mode||"plan"===n.toolPermissionContext.mode&&n.toolPermissionContext.isBypassPermissionsModeAvailable)return{behavior:"allow",updatedInput:getUpdatedInputOrFallback(c,o),decisionReason:{type:"mode",mode:n.toolPermissionContext.mode}};const m=toolAlwaysAllowedRule(n.toolPermissionContext,e);if(m)return{behavior:"allow",updatedInput:getUpdatedInputOrFallback(c,o),decisionReason:{type:"rule",rule:m}};try{const s=i(e),t=await oe(s);if(t){if("allow"===t.behavior)return d(`SQLite permission rule allows ${e.name}: ${t.toolName} (id=${t.id})`),{behavior:"allow",updatedInput:getUpdatedInputOrFallback(c,o),decisionReason:{type:"rule",rule:{source:"userSettings",ruleBehavior:"allow",ruleValue:w(t.toolName)}}};if("deny"===t.behavior)return{behavior:"deny",decisionReason:{type:"rule",rule:{source:"userSettings",ruleBehavior:"deny",ruleValue:w(t.toolName)}},message:`Permission to use ${e.name} has been denied by SQLite rule.`}}}catch{}const f="passthrough"===c.behavior?{...c,behavior:"ask",message:createPermissionRequestMessage(e.name,c.decisionReason)}:c;"ask"===f.behavior&&f.suggestions&&d(`Permission suggestions for ${e.name}: ${Y(f.suggestions,null,2)}`);return f}(o,s,n);if("allow"===R.behavior){const o=n.getAppState();if(e("TRANSCRIPT_CLASSIFIER")){const e=n.localDenialTracking??o.denialTracking;if("auto"===o.toolPermissionContext.mode&&e&&e.consecutiveDenials>0){const o=K(e);persistDenialState(n,o)}}return R}if("ask"===R.behavior){const i=n.getAppState();if("dontAsk"===i.toolPermissionContext.mode)return{behavior:"deny",decisionReason:{type:"mode",mode:"dontAsk"},message:Q(o.name)};if(e("TRANSCRIPT_CLASSIFIER")&&("auto"===i.toolPermissionContext.mode||"plan"===i.toolPermissionContext.mode&&I?.isAutoModeActive())){if("safetyCheck"===R.decisionReason?.type&&!R.decisionReason.classifierApprovable)return i.toolPermissionContext.shouldAvoidPermissionPrompts?{behavior:"deny",message:R.message,decisionReason:{type:"asyncAgent",reason:"Safety check requires interactive approval and permission prompts are not available in this context"}}:R;if(o.requiresUserInteraction?.()&&"ask"===R.behavior)return R;const r=n.localDenialTracking??i.denialTracking??z();if(o.name===u&&!e("POWERSHELL_AUTO_MODE"))return i.toolPermissionContext.shouldAvoidPermissionPrompts?{behavior:"deny",message:"PowerShell tool requires interactive approval",decisionReason:{type:"asyncAgent",reason:"PowerShell tool requires interactive approval and permission prompts are not available in this context"}}:(d(`Skipping auto mode classifier for ${o.name}: tool requires explicit user permission`),R);if("ask"===R.behavior&&o.name!==a&&o.name!==c)try{const e=o.inputSchema.parse(s),t=await o.checkPermissions(e,{...n,getAppState:()=>{const e=n.getAppState();return{...e,toolPermissionContext:{...e.toolPermissionContext,mode:"acceptEdits"}}}});if("allow"===t.behavior){const e=K(r);return persistDenialState(n,e),d(`Skipping auto mode classifier for ${o.name}: would be allowed in acceptEdits mode`),q("tengu_auto_mode_decision",{decision:"allowed",toolName:B(o.name),inProtectedNamespace:F(),agentMsgId:m.message.id,confidence:"high",fastPath:"acceptEdits"}),{behavior:"allow",updatedInput:t.updatedInput??s,decisionReason:{type:"mode",mode:"auto"}}}}catch(e){if(e instanceof p||e instanceof t)throw e}if(x.isAutoModeAllowlistedTool(o.name)){const e=K(r);return persistDenialState(n,e),d(`Skipping auto mode classifier for ${o.name}: tool is on the safe allowlist`),q("tengu_auto_mode_decision",{decision:"allowed",toolName:B(o.name),inProtectedNamespace:F(),agentMsgId:m.message.id,confidence:"high",fastPath:"allowlist"}),{behavior:"allow",updatedInput:s,decisionReason:{type:"mode",mode:"auto"}}}const l=ee(o.name,s);let f;E(v);try{f=await Z(n.messages,l,n.options.tools,i.toolPermissionContext,n.abortController.signal)}finally{_(v)}"ant"===process.env.USER_TYPE&&f.errorDumpPath&&n.addNotification&&n.addNotification({key:"auto-mode-error-dump",text:`Auto mode classifier error — prompts dumped to ${f.errorDumpPath} (included in /share)`,priority:"immediate",color:"error"});const g=f.unavailable?"unavailable":f.shouldBlock?"blocked":"allowed",h=f.usage&&f.model?W(f.model,f.usage):void 0;if(q("tengu_auto_mode_decision",{decision:g,toolName:B(o.name),inProtectedNamespace:F(),agentMsgId:m.message.id,classifierModel:f.model,consecutiveDenials:f.shouldBlock?r.consecutiveDenials+1:0,totalDenials:f.shouldBlock?r.totalDenials+1:r.totalDenials,classifierInputTokens:f.usage?.inputTokens,classifierOutputTokens:f.usage?.outputTokens,classifierCacheReadInputTokens:f.usage?.cacheReadInputTokens,classifierCacheCreationInputTokens:f.usage?.cacheCreationInputTokens,classifierDurationMs:f.durationMs,classifierSystemPromptLength:f.promptLengths?.systemPrompt,classifierToolCallsLength:f.promptLengths?.toolCalls,classifierUserPromptsLength:f.promptLengths?.userPrompts,sessionInputTokens:M(),sessionOutputTokens:N(),sessionCacheReadInputTokens:j(),sessionCacheCreationInputTokens:$(),classifierCostUSD:h,classifierStage:f.stage,classifierStage1InputTokens:f.stage1Usage?.inputTokens,classifierStage1OutputTokens:f.stage1Usage?.outputTokens,classifierStage1CacheReadInputTokens:f.stage1Usage?.cacheReadInputTokens,classifierStage1CacheCreationInputTokens:f.stage1Usage?.cacheCreationInputTokens,classifierStage1DurationMs:f.stage1DurationMs,classifierStage1RequestId:f.stage1RequestId,classifierStage1MsgId:f.stage1MsgId,classifierStage1CostUSD:f.stage1Usage&&f.model?W(f.model,f.stage1Usage):void 0,classifierStage2InputTokens:f.stage2Usage?.inputTokens,classifierStage2OutputTokens:f.stage2Usage?.outputTokens,classifierStage2CacheReadInputTokens:f.stage2Usage?.cacheReadInputTokens,classifierStage2CacheCreationInputTokens:f.stage2Usage?.cacheCreationInputTokens,classifierStage2DurationMs:f.stage2DurationMs,classifierStage2RequestId:f.stage2RequestId,classifierStage2MsgId:f.stage2MsgId,classifierStage2CostUSD:f.stage2Usage&&f.model?W(f.model,f.stage2Usage):void 0}),void 0!==f.durationMs&&D(f.durationMs),f.shouldBlock){if(f.transcriptTooLong){if(i.toolPermissionContext.shouldAvoidPermissionPrompts)throw new p("Agent aborted: auto mode classifier transcript exceeded context window in headless mode");return d("Auto mode classifier transcript too long, falling back to normal permission handling",{level:"warn"}),{...R,decisionReason:{type:"other",reason:"Auto mode classifier transcript exceeded context window — falling back to manual approval"}}}if(f.unavailable)return U("tengu_iron_gate_closed",!0,18e5)?(d("Auto mode classifier unavailable, denying with retry guidance (fail closed)",{level:"warn"}),{behavior:"deny",decisionReason:{type:"classifier",classifier:"auto-mode",reason:"Classifier unavailable"},message:O(o.name,f.model)}):(d("Auto mode classifier unavailable, falling back to normal permission handling (fail open)",{level:"warn"}),R);const e=J(r);persistDenialState(n,e),d(`Auto mode classifier blocked action: ${f.reason}`,{level:"warn"});const s=function(e,o,s,t,i,n,a){if(!X(e))return null;const r=e.totalDenials>=G.maxTotal,l=o.toolPermissionContext.shouldAvoidPermissionPrompts,u=e.totalDenials,c=e.consecutiveDenials,m=r?`${u} actions were blocked this session. Please review the transcript before continuing.`:`${c} consecutive actions were blocked. Please review the transcript before continuing.`;if(q("tengu_auto_mode_denial_limit_exceeded",{limit:r?"total":"consecutive",mode:l?"headless":"cli",messageID:t.message.id,consecutiveDenials:c,totalDenials:u,toolName:B(i.name)}),l)throw new p("Agent aborted: too many classifier denials in headless mode");d(`Classifier denial limit exceeded, falling back to prompting: ${m}`,{level:"warn"}),r&&persistDenialState(a,{...e,totalDenials:0,consecutiveDenials:0});const f="classifier"===n.decisionReason?.type?n.decisionReason.classifier:"auto-mode";return{...n,decisionReason:{type:"classifier",classifier:f,reason:`${m}\n\nLatest blocked action: ${s}`}}}(e,i,f.reason,m,o,R,n);return s||{behavior:"deny",decisionReason:{type:"classifier",classifier:"auto-mode",reason:f.reason},message:H(f.reason)}}const k=K(r);return persistDenialState(n,k),{behavior:"allow",updatedInput:s,decisionReason:{type:"classifier",classifier:"auto-mode",reason:f.reason}}}if(i.toolPermissionContext.shouldAvoidPermissionPrompts){const e=await async function(e,o,s,t,i,n){try{for await(const a of L(e.name,s,o,t,i,n,t.abortController.signal)){if(!a.permissionRequestResult)continue;const s=a.permissionRequestResult;if("allow"===s.behavior){const e=s.updatedInput??o;return s.updatedPermissions?.length&&(S(s.updatedPermissions),t.setAppState(e=>({...e,toolPermissionContext:P(e.toolPermissionContext,s.updatedPermissions)}))),{behavior:"allow",updatedInput:e,decisionReason:{type:"hook",hookName:"PermissionRequest"}}}if("deny"===s.behavior)return s.interrupt&&(d(`Hook interrupt: tool=${e.name} hookMessage=${s.message}`),t.abortController.abort()),{behavior:"deny",message:s.message||"Permission denied by hook",decisionReason:{type:"hook",hookName:"PermissionRequest",reason:s.message}}}}catch(e){g(new Error("PermissionRequest hook failed for headless agent",{cause:f(e)}))}return null}(o,s,v,n,i.toolPermissionContext.mode,R.suggestions);return e||{behavior:"deny",decisionReason:{type:"asyncAgent",reason:"Permission prompts are not available in this context"},message:V(o.name)}}}return R};function persistDenialState(e,o){e.localDenialTracking?Object.assign(e.localDenialTracking,o):e.setAppState(e=>e.denialTracking===o?e:{...e,denialTracking:o})}export async function checkRuleBasedPermissions(e,o,s){const i=s.getAppState(),n=getDenyRuleForTool(i.toolPermissionContext,e);if(n)return{behavior:"deny",decisionReason:{type:"rule",rule:n},message:`Permission to use ${e.name} has been denied.`};const a=getAskRuleForTool(i.toolPermissionContext,e);if(a){if(!(e.name===l&&h.isSandboxingEnabled()&&h.isAutoAllowBashIfSandboxedEnabled()&&r(o)))return{behavior:"ask",decisionReason:{type:"rule",rule:a},message:createPermissionRequestMessage(e.name)}}let u={behavior:"passthrough",message:createPermissionRequestMessage(e.name)};try{const t=e.inputSchema.parse(o);u=await e.checkPermissions(t,s)}catch(e){if(e instanceof p||e instanceof t)throw e;g(e)}return"deny"===u?.behavior||"ask"===u?.behavior&&"rule"===u.decisionReason?.type&&"ask"===u.decisionReason.rule.ruleBehavior||"ask"===u?.behavior&&"safetyCheck"===u.decisionReason?.type?u:null}export async function deletePermissionRule({rule:e,initialContext:o,setToolPermissionContext:s}){if("policySettings"===e.source||"flagSettings"===e.source||"command"===e.source)throw new Error("Cannot delete permission rules from read-only settings");const t=b(o,{type:"removeRules",rules:[e.ruleValue],behavior:e.ruleBehavior,destination:e.source});switch(e.source){case"localSettings":case"userSettings":case"projectSettings":T(e)}s(t)}function convertRulesToUpdates(e,o){const s=new Map;for(const o of e){const e=`${o.source}:${o.ruleBehavior}`;s.has(e)||s.set(e,[]),s.get(e).push(o.ruleValue)}const t=[];for(const[e,i]of s){const[s,n]=e.split(":");t.push({type:o,rules:i,behavior:n,destination:s})}return t}export function applyPermissionRulesToPermissionContext(e,o){const s=convertRulesToUpdates(o,"addRules");return P(e,s)}export function syncPermissionRulesFromDisk(e,o){let s=e;if(A()){const e=["userSettings","projectSettings","localSettings","cliArg","session"],o=["allow","deny","ask"];for(const t of e)for(const e of o)s=b(s,{type:"replaceRules",rules:[],behavior:e,destination:t})}const t=["userSettings","projectSettings","localSettings"];for(const e of t)for(const o of["allow","deny","ask"])s=b(s,{type:"replaceRules",rules:[],behavior:o,destination:e});const i=convertRulesToUpdates(o,"replaceRules");return P(s,i)}function getUpdatedInputOrFallback(e,o){return("updatedInput"in e?e.updatedInput:void 0)??o}
1
+ import{feature as e}from"../../recovery/bunBundleShim.js";import{createRequire as o}from"module";const s=o(import.meta.url);import{APIUserAbortError as t}from"@anthropic-ai/sdk/index.js";import{getToolNameForPermissionCheck as i,mcpInfoFromString as n}from"../../services/mcp/mcpStringUtils.js";import{AGENT_TOOL_NAME as a}from"../../tools/AgentTool/constants.js";import{shouldUseSandbox as r}from"../../tools/BashTool/shouldUseSandbox.js";import{BASH_TOOL_NAME as l}from"../../tools/BashTool/toolName.js";import{POWERSHELL_TOOL_NAME as u}from"../../tools/PowerShellTool/toolName.js";import{REPL_TOOL_NAME as c}from"../../tools/REPLTool/constants.js";import{extractOutputRedirections as m}from"../bash/commands.js";import{logForDebugging as d}from"../debug.js";import{AbortError as p,toError as f}from"../errors.js";import{logError as g}from"../log.js";import{SandboxManager as h}from"../sandbox/sandbox-adapter.js";import{getSettingSourceDisplayNameLowercase as v,SETTING_SOURCES as R}from"../settings/constants.js";import{plural as k}from"../stringUtils.js";import{permissionModeTitle as y}from"./PermissionMode.js";import{applyPermissionUpdate as b,applyPermissionUpdates as P,persistPermissionUpdates as S}from"./PermissionUpdate.js";import{permissionRuleValueFromString as w,permissionRuleValueToString as C}from"./permissionRuleParser.js";import{deletePermissionRuleFromSettings as T,shouldAllowManagedPermissionRulesOnly as A}from"./permissionsLoader.js";const x=e("TRANSCRIPT_CLASSIFIER")?s("./classifierDecision.js"):null,I=e("TRANSCRIPT_CLASSIFIER")?s("./autoModeState.js"):null;import{addToTurnClassifierDuration as D,getTotalCacheCreationInputTokens as $,getTotalCacheReadInputTokens as j,getTotalInputTokens as M,getTotalOutputTokens as N}from"../../bootstrap/state.js";import{getFeatureValue_CACHED_WITH_REFRESH as U}from"../../services/analytics/growthbook.js";import{logEvent as q}from"../../services/analytics/index.js";import{sanitizeToolNameForAnalytics as B}from"../../services/analytics/metadata.js";import{clearClassifierChecking as _,setClassifierChecking as E}from"../classifierApprovals.js";import{isInProtectedNamespace as F}from"../envUtils.js";import{executePermissionRequestHooks as L}from"../hooks.js";import{AUTO_REJECT_MESSAGE as V,buildClassifierUnavailableMessage as O,buildYoloRejectionMessage as H,DONT_ASK_REJECT_MESSAGE as Q}from"../messages.js";import{calculateCostFromTokens as W}from"../modelCost.js";import{jsonStringify as Y}from"../slowOperations.js";import{createDenialTrackingState as z,DENIAL_LIMITS as G,recordDenial as J,recordSuccess as K,shouldFallbackToPrompting as X}from"./denialTracking.js";import{classifyYoloAction as Z,formatActionForClassifier as ee}from"./yoloClassifier.js";import{findPermissionRule as oe}from"./permissionsDb.js";const se=[...R,"cliArg","command","session"];export function permissionRuleSourceDisplayString(e){return v(e)}export function getAllowRules(e){return se.flatMap(o=>(e.alwaysAllowRules[o]||[]).map(e=>({source:o,ruleBehavior:"allow",ruleValue:w(e)})))}export function createPermissionRequestMessage(o,s){if(s){if((e("BASH_CLASSIFIER")||e("TRANSCRIPT_CLASSIFIER"))&&"classifier"===s.type)return`Classifier '${s.classifier}' requires approval for this ${o} command: ${s.reason}`;switch(s.type){case"hook":return s.reason?`Hook '${s.hookName}' blocked this action: ${s.reason}`:`Hook '${s.hookName}' requires approval for this ${o} command`;case"rule":return`Permission rule '${C(s.rule.ruleValue)}' from ${permissionRuleSourceDisplayString(s.rule.source)} requires approval for this ${o} command`;case"subcommandResults":{const e=[];for(const[t,i]of s.reasons)if("ask"===i.behavior||"passthrough"===i.behavior)if("Bash"===o){const{commandWithoutRedirections:o,redirections:s}=m(t),i=s.length>0?o:t;e.push(i)}else e.push(t);if(e.length>0){const s=e.length;return`This ${o} command contains multiple operations. The following ${k(s,"part")} ${k(s,"requires","require")} approval: ${e.join(", ")}`}return`This ${o} command contains multiple operations that require approval`}case"permissionPromptTool":return`Tool '${s.permissionPromptToolName}' requires approval for this ${o} command`;case"sandboxOverride":return"Run outside of the sandbox";case"workingDir":case"safetyCheck":case"other":case"asyncAgent":return s.reason;case"mode":return`Current permission mode (${y(s.mode)}) requires approval for this ${o} command`}}return`Context solicitó permisos para usar ${o}, pero aún no se los has otorgado.`}export function getDenyRules(e){return se.flatMap(o=>(e.alwaysDenyRules[o]||[]).map(e=>({source:o,ruleBehavior:"deny",ruleValue:w(e)})))}export function getAskRules(e){return se.flatMap(o=>(e.alwaysAskRules[o]||[]).map(e=>({source:o,ruleBehavior:"ask",ruleValue:w(e)})))}function toolMatchesRule(e,o){if(void 0!==o.ruleValue.ruleContent)return!1;const s=i(e);if(o.ruleValue.toolName===s)return!0;const t=n(o.ruleValue.toolName),a=n(s);return null!==t&&null!==a&&(void 0===t.toolName||"*"===t.toolName)&&t.serverName===a.serverName}export function toolAlwaysAllowedRule(e,o){return getAllowRules(e).find(e=>toolMatchesRule(o,e))||null}export function getDenyRuleForTool(e,o){return getDenyRules(e).find(e=>toolMatchesRule(o,e))||null}export function getAskRuleForTool(e,o){return getAskRules(e).find(e=>toolMatchesRule(o,e))||null}export function getDenyRuleForAgent(e,o,s){return getDenyRules(e).find(e=>e.ruleValue.toolName===o&&e.ruleValue.ruleContent===s)||null}export function filterDeniedAgents(e,o,s){const t=new Set;for(const e of getDenyRules(o))e.ruleValue.toolName===s&&void 0!==e.ruleValue.ruleContent&&t.add(e.ruleValue.ruleContent);return e.filter(e=>!t.has(e.agentType))}export function getRuleByContentsForTool(e,o,s){return getRuleByContentsForToolName(e,i(o),s)}export function getRuleByContentsForToolName(e,o,s){const t=new Map;let i=[];switch(s){case"allow":i=getAllowRules(e);break;case"deny":i=getDenyRules(e);break;case"ask":i=getAskRules(e)}for(const e of i)e.ruleValue.toolName===o&&void 0!==e.ruleValue.ruleContent&&e.ruleBehavior===s&&t.set(e.ruleValue.ruleContent,e);return t}export const hasPermissionsToUseTool=async(o,s,n,m,v)=>{const R=await async function(e,o,s){if(s.abortController.signal.aborted)throw new p;let n=s.getAppState();const a=getDenyRuleForTool(n.toolPermissionContext,e);if(a)return{behavior:"deny",decisionReason:{type:"rule",rule:a},message:`Permission to use ${e.name} has been denied.`};const u=getAskRuleForTool(n.toolPermissionContext,e);if(u){if(!(e.name===l&&h.isSandboxingEnabled()&&h.isAutoAllowBashIfSandboxedEnabled()&&r(o)))return{behavior:"ask",decisionReason:{type:"rule",rule:u},message:createPermissionRequestMessage(e.name)}}let c={behavior:"passthrough",message:createPermissionRequestMessage(e.name)};try{const t=e.inputSchema.parse(o);c=await e.checkPermissions(t,s)}catch(e){if(e instanceof p||e instanceof t)throw e;g(e)}if("deny"===c?.behavior)return c;if(e.requiresUserInteraction?.()&&"ask"===c?.behavior)return c;if("ask"===c?.behavior&&"rule"===c.decisionReason?.type&&"ask"===c.decisionReason.rule.ruleBehavior)return c;if("ask"===c?.behavior&&"safetyCheck"===c.decisionReason?.type)return c;n=s.getAppState();if("bypassPermissions"===n.toolPermissionContext.mode||"plan"===n.toolPermissionContext.mode&&n.toolPermissionContext.isBypassPermissionsModeAvailable)return{behavior:"allow",updatedInput:getUpdatedInputOrFallback(c,o),decisionReason:{type:"mode",mode:n.toolPermissionContext.mode}};const m=toolAlwaysAllowedRule(n.toolPermissionContext,e);if(m)return{behavior:"allow",updatedInput:getUpdatedInputOrFallback(c,o),decisionReason:{type:"rule",rule:m}};try{const s=i(e),t=await oe(s);if(t){if("allow"===t.behavior)return d(`SQLite permission rule allows ${e.name}: ${t.toolName} (id=${t.id})`),{behavior:"allow",updatedInput:getUpdatedInputOrFallback(c,o),decisionReason:{type:"rule",rule:{source:"userSettings",ruleBehavior:"allow",ruleValue:w(t.toolName)}}};if("deny"===t.behavior)return{behavior:"deny",decisionReason:{type:"rule",rule:{source:"userSettings",ruleBehavior:"deny",ruleValue:w(t.toolName)}},message:`Permission to use ${e.name} has been denied by SQLite rule.`}}}catch{}const f="passthrough"===c.behavior?{...c,behavior:"ask",message:createPermissionRequestMessage(e.name,c.decisionReason)}:c;"ask"===f.behavior&&f.suggestions&&d(`Permission suggestions for ${e.name}: ${Y(f.suggestions,null,2)}`);return f}(o,s,n);if("allow"===R.behavior){const o=n.getAppState();if(e("TRANSCRIPT_CLASSIFIER")){const e=n.localDenialTracking??o.denialTracking;if("auto"===o.toolPermissionContext.mode&&e&&e.consecutiveDenials>0){const o=K(e);persistDenialState(n,o)}}return R}if("ask"===R.behavior){const i=n.getAppState();if("dontAsk"===i.toolPermissionContext.mode)return{behavior:"deny",decisionReason:{type:"mode",mode:"dontAsk"},message:Q(o.name)};if(e("TRANSCRIPT_CLASSIFIER")&&("auto"===i.toolPermissionContext.mode||"plan"===i.toolPermissionContext.mode&&I?.isAutoModeActive())){if("safetyCheck"===R.decisionReason?.type&&!R.decisionReason.classifierApprovable)return i.toolPermissionContext.shouldAvoidPermissionPrompts?{behavior:"deny",message:R.message,decisionReason:{type:"asyncAgent",reason:"Safety check requires interactive approval and permission prompts are not available in this context"}}:R;if(o.requiresUserInteraction?.()&&"ask"===R.behavior)return R;const r=n.localDenialTracking??i.denialTracking??z();if(o.name===u&&!e("POWERSHELL_AUTO_MODE"))return i.toolPermissionContext.shouldAvoidPermissionPrompts?{behavior:"deny",message:"PowerShell tool requires interactive approval",decisionReason:{type:"asyncAgent",reason:"PowerShell tool requires interactive approval and permission prompts are not available in this context"}}:(d(`Skipping auto mode classifier for ${o.name}: tool requires explicit user permission`),R);if("ask"===R.behavior&&o.name!==a&&o.name!==c)try{const e=o.inputSchema.parse(s),t=await o.checkPermissions(e,{...n,getAppState:()=>{const e=n.getAppState();return{...e,toolPermissionContext:{...e.toolPermissionContext,mode:"acceptEdits"}}}});if("allow"===t.behavior){const e=K(r);return persistDenialState(n,e),d(`Skipping auto mode classifier for ${o.name}: would be allowed in acceptEdits mode`),q("tengu_auto_mode_decision",{decision:"allowed",toolName:B(o.name),inProtectedNamespace:F(),agentMsgId:m.message.id,confidence:"high",fastPath:"acceptEdits"}),{behavior:"allow",updatedInput:t.updatedInput??s,decisionReason:{type:"mode",mode:"auto"}}}}catch(e){if(e instanceof p||e instanceof t)throw e}if(x.isAutoModeAllowlistedTool(o.name)){const e=K(r);return persistDenialState(n,e),d(`Skipping auto mode classifier for ${o.name}: tool is on the safe allowlist`),q("tengu_auto_mode_decision",{decision:"allowed",toolName:B(o.name),inProtectedNamespace:F(),agentMsgId:m.message.id,confidence:"high",fastPath:"allowlist"}),{behavior:"allow",updatedInput:s,decisionReason:{type:"mode",mode:"auto"}}}const l=ee(o.name,s);let f;E(v);try{f=await Z(n.messages,l,n.options.tools,i.toolPermissionContext,n.abortController.signal)}finally{_(v)}"ant"===process.env.USER_TYPE&&f.errorDumpPath&&n.addNotification&&n.addNotification({key:"auto-mode-error-dump",text:`Auto mode classifier error — prompts dumped to ${f.errorDumpPath} (included in /share)`,priority:"immediate",color:"error"});const g=f.unavailable?"unavailable":f.shouldBlock?"blocked":"allowed",h=f.usage&&f.model?W(f.model,f.usage):void 0;if(q("tengu_auto_mode_decision",{decision:g,toolName:B(o.name),inProtectedNamespace:F(),agentMsgId:m.message.id,classifierModel:f.model,consecutiveDenials:f.shouldBlock?r.consecutiveDenials+1:0,totalDenials:f.shouldBlock?r.totalDenials+1:r.totalDenials,classifierInputTokens:f.usage?.inputTokens,classifierOutputTokens:f.usage?.outputTokens,classifierCacheReadInputTokens:f.usage?.cacheReadInputTokens,classifierCacheCreationInputTokens:f.usage?.cacheCreationInputTokens,classifierDurationMs:f.durationMs,classifierSystemPromptLength:f.promptLengths?.systemPrompt,classifierToolCallsLength:f.promptLengths?.toolCalls,classifierUserPromptsLength:f.promptLengths?.userPrompts,sessionInputTokens:M(),sessionOutputTokens:N(),sessionCacheReadInputTokens:j(),sessionCacheCreationInputTokens:$(),classifierCostUSD:h,classifierStage:f.stage,classifierStage1InputTokens:f.stage1Usage?.inputTokens,classifierStage1OutputTokens:f.stage1Usage?.outputTokens,classifierStage1CacheReadInputTokens:f.stage1Usage?.cacheReadInputTokens,classifierStage1CacheCreationInputTokens:f.stage1Usage?.cacheCreationInputTokens,classifierStage1DurationMs:f.stage1DurationMs,classifierStage1RequestId:f.stage1RequestId,classifierStage1MsgId:f.stage1MsgId,classifierStage1CostUSD:f.stage1Usage&&f.model?W(f.model,f.stage1Usage):void 0,classifierStage2InputTokens:f.stage2Usage?.inputTokens,classifierStage2OutputTokens:f.stage2Usage?.outputTokens,classifierStage2CacheReadInputTokens:f.stage2Usage?.cacheReadInputTokens,classifierStage2CacheCreationInputTokens:f.stage2Usage?.cacheCreationInputTokens,classifierStage2DurationMs:f.stage2DurationMs,classifierStage2RequestId:f.stage2RequestId,classifierStage2MsgId:f.stage2MsgId,classifierStage2CostUSD:f.stage2Usage&&f.model?W(f.model,f.stage2Usage):void 0}),void 0!==f.durationMs&&D(f.durationMs),f.shouldBlock){if(f.transcriptTooLong){if(i.toolPermissionContext.shouldAvoidPermissionPrompts)throw new p("Agent aborted: auto mode classifier transcript exceeded context window in headless mode");return d("Auto mode classifier transcript too long, falling back to normal permission handling",{level:"warn"}),{...R,decisionReason:{type:"other",reason:"Auto mode classifier transcript exceeded context window — falling back to manual approval"}}}if(f.unavailable)return U("tengu_iron_gate_closed",!0,18e5)?(d("Auto mode classifier unavailable, denying with retry guidance (fail closed)",{level:"warn"}),{behavior:"deny",decisionReason:{type:"classifier",classifier:"auto-mode",reason:"Classifier unavailable"},message:O(o.name,f.model)}):(d("Auto mode classifier unavailable, falling back to normal permission handling (fail open)",{level:"warn"}),R);const e=J(r);persistDenialState(n,e),d(`Auto mode classifier blocked action: ${f.reason}`,{level:"warn"});const s=function(e,o,s,t,i,n,a){if(!X(e))return null;const r=e.totalDenials>=G.maxTotal,l=o.toolPermissionContext.shouldAvoidPermissionPrompts,u=e.totalDenials,c=e.consecutiveDenials,m=r?`${u} actions were blocked this session. Please review the transcript before continuing.`:`${c} consecutive actions were blocked. Please review the transcript before continuing.`;if(q("tengu_auto_mode_denial_limit_exceeded",{limit:r?"total":"consecutive",mode:l?"headless":"cli",messageID:t.message.id,consecutiveDenials:c,totalDenials:u,toolName:B(i.name)}),l)throw new p("Agent aborted: too many classifier denials in headless mode");d(`Classifier denial limit exceeded, falling back to prompting: ${m}`,{level:"warn"}),r&&persistDenialState(a,{...e,totalDenials:0,consecutiveDenials:0});const f="classifier"===n.decisionReason?.type?n.decisionReason.classifier:"auto-mode";return{...n,decisionReason:{type:"classifier",classifier:f,reason:`${m}\n\nLatest blocked action: ${s}`}}}(e,i,f.reason,m,o,R,n);return s||{behavior:"deny",decisionReason:{type:"classifier",classifier:"auto-mode",reason:f.reason},message:H(f.reason)}}const k=K(r);return persistDenialState(n,k),{behavior:"allow",updatedInput:s,decisionReason:{type:"classifier",classifier:"auto-mode",reason:f.reason}}}if(i.toolPermissionContext.shouldAvoidPermissionPrompts){const e=await async function(e,o,s,t,i,n){try{for await(const a of L(e.name,s,o,t,i,n,t.abortController.signal)){if(!a.permissionRequestResult)continue;const s=a.permissionRequestResult;if("allow"===s.behavior){const e=s.updatedInput??o;return s.updatedPermissions?.length&&(S(s.updatedPermissions),t.setAppState(e=>({...e,toolPermissionContext:P(e.toolPermissionContext,s.updatedPermissions)}))),{behavior:"allow",updatedInput:e,decisionReason:{type:"hook",hookName:"PermissionRequest"}}}if("deny"===s.behavior)return s.interrupt&&(d(`Hook interrupt: tool=${e.name} hookMessage=${s.message}`),t.abortController.abort()),{behavior:"deny",message:s.message||"Permission denied by hook",decisionReason:{type:"hook",hookName:"PermissionRequest",reason:s.message}}}}catch(e){g(new Error("PermissionRequest hook failed for headless agent",{cause:f(e)}))}return null}(o,s,v,n,i.toolPermissionContext.mode,R.suggestions);return e||{behavior:"deny",decisionReason:{type:"asyncAgent",reason:"Permission prompts are not available in this context"},message:V(o.name)}}}return R};function persistDenialState(e,o){e.localDenialTracking?Object.assign(e.localDenialTracking,o):e.setAppState(e=>e.denialTracking===o?e:{...e,denialTracking:o})}export async function checkRuleBasedPermissions(e,o,s){const i=s.getAppState(),n=getDenyRuleForTool(i.toolPermissionContext,e);if(n)return{behavior:"deny",decisionReason:{type:"rule",rule:n},message:`Permission to use ${e.name} has been denied.`};const a=getAskRuleForTool(i.toolPermissionContext,e);if(a){if(!(e.name===l&&h.isSandboxingEnabled()&&h.isAutoAllowBashIfSandboxedEnabled()&&r(o)))return{behavior:"ask",decisionReason:{type:"rule",rule:a},message:createPermissionRequestMessage(e.name)}}let u={behavior:"passthrough",message:createPermissionRequestMessage(e.name)};try{const t=e.inputSchema.parse(o);u=await e.checkPermissions(t,s)}catch(e){if(e instanceof p||e instanceof t)throw e;g(e)}return"deny"===u?.behavior||"ask"===u?.behavior&&"rule"===u.decisionReason?.type&&"ask"===u.decisionReason.rule.ruleBehavior||"ask"===u?.behavior&&"safetyCheck"===u.decisionReason?.type?u:null}export async function deletePermissionRule({rule:e,initialContext:o,setToolPermissionContext:s}){if("policySettings"===e.source||"flagSettings"===e.source||"command"===e.source)throw new Error("Cannot delete permission rules from read-only settings");const t=b(o,{type:"removeRules",rules:[e.ruleValue],behavior:e.ruleBehavior,destination:e.source});switch(e.source){case"localSettings":case"userSettings":case"projectSettings":T(e)}s(t)}function convertRulesToUpdates(e,o){const s=new Map;for(const o of e){const e=`${o.source}:${o.ruleBehavior}`;s.has(e)||s.set(e,[]),s.get(e).push(o.ruleValue)}const t=[];for(const[e,i]of s){const[s,n]=e.split(":");t.push({type:o,rules:i,behavior:n,destination:s})}return t}export function applyPermissionRulesToPermissionContext(e,o){const s=convertRulesToUpdates(o,"addRules");return P(e,s)}export function syncPermissionRulesFromDisk(e,o){let s=e;if(A()){const e=["userSettings","projectSettings","localSettings","cliArg","session"],o=["allow","deny","ask"];for(const t of e)for(const e of o)s=b(s,{type:"replaceRules",rules:[],behavior:e,destination:t})}const t=["userSettings","projectSettings","localSettings"];for(const e of t)for(const o of["allow","deny","ask"])s=b(s,{type:"replaceRules",rules:[],behavior:o,destination:e});const i=convertRulesToUpdates(o,"replaceRules");return P(s,i)}function getUpdatedInputOrFallback(e,o){return("updatedInput"in e?e.updatedInput:void 0)??o}
@@ -1 +1 @@
1
- import{feature as t}from"../../recovery/bunBundleShim.js";import{randomUUID as o}from"crypto";import{setPromptId as e}from"../../bootstrap/state.js";import{builtInCommandNames as s,findCommand as n,getCommand as a,getCommandName as r,hasCommand as m}from"../../commands.js";import{NO_CONTENT_MESSAGE as i}from"../../constants/messages.js";import{addInvokedSkill as l,getSessionId as p}from"../../bootstrap/state.js";import{COMMAND_MESSAGE_TAG as u,COMMAND_NAME_TAG as c}from"../../constants/xml.js";import{logEvent as d}from"../../services/analytics/index.js";import{getDumpPromptsPath as g}from"../../services/api/dumpPrompts.js";import{buildPostCompactMessages as f}from"../../services/compact/compact.js";import{resetMicrocompactState as h}from"../../services/compact/microCompact.js";import{runAgent as y}from"../../tools/AgentTool/runAgent.js";import{renderToolUseProgressMessage as k}from"../../tools/AgentTool/UI.js";import{createAbortController as I}from"../abortController.js";import{getAgentContext as _}from"../agentContext.js";import{createAttachmentMessage as j,getAttachmentMessages as T}from"../attachments.js";import{logForDebugging as w}from"../debug.js";import{isEnvTruthy as S}from"../envUtils.js";import{AbortError as $,MalformedCommandError as x}from"../errors.js";import{getDisplayPath as v}from"../file.js";import{extractResultText as C,prepareForkedCommandContext as M}from"../forkedAgent.js";import{getFsImplementation as O}from"../fsOperations.js";import{isFullscreenEnvEnabled as b}from"../fullscreen.js";import{toArray as Q}from"../generators.js";import{registerSkillHooks as A}from"../hooks/registerSkillHooks.js";import{logError as D}from"../log.js";import{enqueuePendingNotification as U}from"../messageQueueManager.js";import{createCommandInputMessage as P,createSyntheticUserCaveatMessage as R,createSystemMessage as L,createUserInterruptionMessage as B,createUserMessage as N,formatCommandInputTags as E,isCompactBoundaryMessage as F,isSystemLocalCommandMessage as W,normalizeMessages as H,prepareUserContent as X}from"../messages.js";import{parseToolListFromCLI as J}from"../permissions/permissionSetup.js";import{hasPermissionsToUseTool as K}from"../permissions/permissions.js";import{isOfficialMarketplaceName as q,parsePluginIdentifier as z}from"../plugins/pluginIdentifier.js";import{isRestrictedToPluginOnly as G,isSourceAdminTrusted as Z}from"../settings/pluginOnlyPolicy.js";import{parseSlashCommand as V}from"../slashCommandParsing.js";import{sleep as Y}from"../sleep.js";import{recordSkillUsage as tt}from"../suggestions/skillUsageTracking.js";import{logOTelEvent as ot,redactIfDisabled as et}from"../telemetry/events.js";import{buildPluginCommandTelemetryFields as st}from"../telemetry/pluginTelemetry.js";import{getAssistantMessageContentLength as nt}from"../tokens.js";import{createAgentId as at}from"../uuid.js";import{getWorkload as rt}from"../workloadContext.js";export function looksLikeCommand(t){return!/[^a-zA-Z0-9:\-_]/.test(t)}export async function processSlashCommand(n,l,p,u,c,g,_,j,T){const S=V(n);if(!S){d("tengu_input_slash_missing",{});const t="Commands are in the form `/command [args]`";return{messages:[R(),...u,N({content:X({inputString:t,precedingInputBlocks:l})})],shouldQuery:!1,resultText:t}}const{commandName:v,args:Q,isMcp:A}=S,E=A?"mcp":s().has(v)?v:"custom";if(!m(v,c.options.commands)){let t=!1;try{await O().stat(`/${v}`),t=!0}catch{}if(looksLikeCommand(v)&&!t){d("tengu_input_slash_invalid",{input:v});const t=`Unknown skill: ${v}`;return{messages:[R(),...u,N({content:X({inputString:t,precedingInputBlocks:l})}),...Q?[L(`Args from unknown skill: ${Q}`,"warning")]:[]],shouldQuery:!1,resultText:t}}const s=o();return e(s),d("tengu_input_prompt",{}),ot("user_prompt",{prompt_length:String(n.length),prompt:et(n),"prompt.id":s}),{messages:[N({content:X({inputString:n,precedingInputBlocks:l}),uuid:_}),...u],shouldQuery:!0}}const{messages:J,shouldQuery:G,allowedTools:Z,model:mt,effort:it,command:lt,resultText:pt,nextInput:ut,submitNextInput:ct,jumpToMessageId:dt}=await async function(e,s,n,m,l,p,u,c,g){const _=a(e,m.options.commands);"prompt"===_.type&&!1!==_.userInvocable&&tt(e);if(!1===_.userInvocable)return{messages:[N({content:X({inputString:`/${e}`,precedingInputBlocks:l})}),N({content:`This skill can only be invoked by Claude, not directly by users. Ask Claude to use the "${e}" skill for you.`})],shouldQuery:!1,command:_};try{switch(_.type){case"local-jsx":return new Promise(t=>{let o=!1;const e=setTimeout(()=>{o||(o=!0,n({jsx:null,shouldHidePromptInput:!1,clearLocalJSX:!0}),t({messages:[],shouldQuery:!1,command:_}))},3e4),onDone=(n,a)=>{if(o=!0,clearTimeout(e),"skip"===a?.display)return void t({messages:[],shouldQuery:!1,command:_,nextInput:a?.nextInput,submitNextInput:a?.submitNextInput,jumpToMessageId:a?.jumpToMessageId});const r=(a?.metaMessages??[]).map(t=>N({content:t,isMeta:!0})),m=b()&&"string"==typeof n&&n.endsWith(" dismissed");t({messages:"system"===a?.display?m?r:[P(formatCommandInput(_,s)),P(`<local-command-stdout>${n}</local-command-stdout>`),...r]:[N({content:X({inputString:formatCommandInput(_,s),precedingInputBlocks:l})}),N(n?{content:`<local-command-stdout>${n}</local-command-stdout>`}:{content:`<local-command-stdout>${i}</local-command-stdout>`}),...r],shouldQuery:a?.shouldQuery??!1,command:_,nextInput:a?.nextInput,submitNextInput:a?.submitNextInput,jumpToMessageId:a?.jumpToMessageId})};_.load().then(t=>t.call(onDone,{...m,canUseTool:c},s)).then(e=>{null!=e&&(m.options.isNonInteractiveSession?t({messages:[],shouldQuery:!1,command:_}):o||n({jsx:e,shouldHidePromptInput:!0,showSpinner:!1,isLocalJSXCommand:!0,isImmediate:!0===_.immediate}))}).catch(s=>{D(s),o||(o=!0,clearTimeout(e),n({jsx:null,shouldHidePromptInput:!1,clearLocalJSX:!0}),t({messages:[],shouldQuery:!1,command:_}))})});case"local":{const t=_.isSensitive&&s.trim()?"***":s,o=N({content:X({inputString:formatCommandInput(_,t),precedingInputBlocks:l})});try{const t=R(),e=await _.load(),n=await e.call(s,m);if("skip"===n.type)return{messages:[],shouldQuery:!1,command:_};if("compact"===n.type){const e=[t,o,...n.displayText?[N({content:`<local-command-stdout>${n.displayText}</local-command-stdout>`,timestamp:new Date(Date.now()+100).toISOString()})]:[]],s={...n.compactionResult,messagesToKeep:[...n.compactionResult.messagesToKeep??[],...e]};return h(),{messages:f(s),shouldQuery:!1,command:_}}return{messages:[o,P(`<local-command-stdout>${n.value}</local-command-stdout>`)],shouldQuery:!1,command:_,resultText:n.value}}catch(t){return D(t),{messages:[o,P(`<local-command-stderr>${String(t)}</local-command-stderr>`)],shouldQuery:!1,command:_}}}case"prompt":try{return"fork"===_.context?await async function(e,s,n,a,m,i){const l=at(),p=e.pluginInfo?z(e.pluginInfo.repository).marketplace:void 0;d("tengu_slash_command_forked",{command_name:e.name,invocation_trigger:"user-slash",...e.pluginInfo&&{_PROTO_plugin_name:e.pluginInfo.pluginManifest.name,...p&&{_PROTO_marketplace_name:p},...st(e.pluginInfo)}});const{skillContent:u,modifiedGetAppState:c,baseAgent:g,promptMessages:f}=await M(e,s,n),h=void 0!==e.effort?{...g,effort:e.effort}:g;if(w(`Executing forked slash command /${e.name} with agent ${h.agentType}`),t("KAIROS")&&(await n.getAppState()).kairosEnabled){const t=I(),o=r(e),s=rt(),enqueueResult=t=>U({value:t,mode:"prompt",priority:"later",isMeta:!0,skipSlashCommands:!0,workload:s});return(async()=>{const s=Date.now()+1e4;for(;Date.now()<s&&n.getAppState().mcp.clients.some(t=>"pending"===t.type);)await Y(200);const a=n.options.refreshTools?.()??n.options.tools,r=[];for await(const o of y({agentDefinition:h,promptMessages:f,toolUseContext:{...n,getAppState:c,abortController:t},canUseTool:i,isAsync:!0,querySource:"agent:custom",model:e.model,availableTools:a,override:{agentId:l}}))r.push(o);const m=C(r,"Command completed");w(`Background forked command /${o} completed (agent ${l})`),enqueueResult(`<scheduled-task-result command="/${o}">\n${m}\n</scheduled-task-result>`)})().catch(t=>{D(t),enqueueResult(`<scheduled-task-result command="/${o}" status="failed">\n${t instanceof Error?t.message:String(t)}\n</scheduled-task-result>`)}),{messages:[],shouldQuery:!1,command:e}}const _=[],j=[],T=`forked-command-${e.name}`;let S=0;const createProgressMessage=t=>(S++,{type:"progress",data:{message:t,type:"agent_progress",prompt:u,agentId:l},parentToolUseID:T,toolUseID:`${T}-${S}`,timestamp:(new Date).toISOString(),uuid:o()}),updateProgress=()=>{m({jsx:k(j,{tools:n.options.tools,verbose:!1}),shouldHidePromptInput:!1,shouldContinueAnimation:!0,showSpinner:!0})};updateProgress();try{for await(const t of y({agentDefinition:h,promptMessages:f,toolUseContext:{...n,getAppState:c},canUseTool:i,isAsync:!1,querySource:"agent:custom",model:e.model,availableTools:n.options.tools})){_.push(t);const o=H([t]);if("assistant"===t.type){const e=nt(t);e>0&&n.setResponseLength(t=>t+e);const s=o[0];s&&"assistant"===s.type&&(j.push(createProgressMessage(t)),updateProgress())}if("user"===t.type){const t=o[0];t&&"user"===t.type&&(j.push(createProgressMessage(t)),updateProgress())}}}finally{m(null)}let $=C(_,"Command completed");return w(`Forked slash command /${e.name} completed with agent ${l}`),{messages:[N({content:X({inputString:`/${r(e)} ${s}`.trim(),precedingInputBlocks:a})}),N({content:`<local-command-stdout>\n${$}\n</local-command-stdout>`})],shouldQuery:!1,command:e,resultText:$}}(_,s,m,l,n,c??K):await getMessagesForPromptSlashCommand(_,s,m,l,p,g)}catch(t){return t instanceof $?{messages:[N({content:X({inputString:formatCommandInput(_,s),precedingInputBlocks:l})}),B({toolUse:!1})],shouldQuery:!1,command:_}:{messages:[N({content:X({inputString:formatCommandInput(_,s),precedingInputBlocks:l})}),N({content:`<local-command-stderr>${String(t)}</local-command-stderr>`})],shouldQuery:!1,command:_}}}}catch(t){if(t instanceof x)return{messages:[N({content:X({inputString:t.message,precedingInputBlocks:l})})],shouldQuery:!1,command:_};throw t}}(v,Q,g,c,l,p,0,T,_);if(0===J.length){const t={input:E};if("prompt"===lt.type&&lt.pluginInfo){const{pluginManifest:o,repository:e}=lt.pluginInfo,{marketplace:s}=z(e),n=q(s);t._PROTO_plugin_name=o.name,s&&(t._PROTO_marketplace_name=s),t.plugin_repository=n?e:"third-party",t.plugin_name=n?o.name:"third-party",n&&o.version&&(t.plugin_version=o.version),Object.assign(t,st(lt.pluginInfo))}return d("tengu_input_command",{...t,invocation_trigger:"user-slash"}),{messages:[],shouldQuery:!1,model:mt,nextInput:ut,submitNextInput:ct,jumpToMessageId:dt}}if(2===J.length&&"user"===J[1].type&&"string"==typeof J[1].message.content&&J[1].message.content.startsWith("Unknown command:")){return n.startsWith("/var")||n.startsWith("/tmp")||n.startsWith("/private")||d("tengu_input_slash_invalid",{input:v}),{messages:[R(),...J],shouldQuery:G,allowedTools:Z,model:mt}}const gt={input:E};if("prompt"===lt.type&&lt.pluginInfo){const{pluginManifest:t,repository:o}=lt.pluginInfo,{marketplace:e}=z(o),s=q(e);gt._PROTO_plugin_name=t.name,e&&(gt._PROTO_marketplace_name=e),gt.plugin_repository=s?o:"third-party",gt.plugin_name=s?t.name:"third-party",s&&t.version&&(gt.plugin_version=t.version),Object.assign(gt,st(lt.pluginInfo))}d("tengu_input_command",{...gt,invocation_trigger:"user-slash"});const ft=J.length>0&&J[0]&&F(J[0]);return{messages:G||J.every(W)||ft?J:[R(),...J],shouldQuery:G,allowedTools:Z,model:mt,effort:it,resultText:pt,nextInput:ut,submitNextInput:ct,jumpToMessageId:dt}}function formatCommandInput(t,o){return E(r(t),o)}export function formatSkillLoadingMetadata(t,o="loading"){return[`<${u}>${t}</${u}>`,`<${c}>${t}</${c}>`,"<skill-format>true</skill-format>"].join("\n")}function formatSlashCommandLoadingMetadata(t,o){return[`<${u}>${t}</${u}>`,`<${c}>/${t}</${c}>`,o?`<command-args>${o}</command-args>`:null].filter(Boolean).join("\n")}function formatCommandLoadingMetadata(t,o){return!1!==t.userInvocable?formatSlashCommandLoadingMetadata(t.name,o):"skills"===t.loadedFrom||"plugin"===t.loadedFrom||"mcp"===t.loadedFrom?formatSkillLoadingMetadata(t.name,t.progressMessage):formatSlashCommandLoadingMetadata(t.name,o)}export async function processPromptSlashCommand(t,o,e,s,a=[]){const r=n(t,e);if(!r)throw new x(`Unknown command: ${t}`);if("prompt"!==r.type)throw new Error(`Unexpected ${r.type} command. Expected 'prompt' command. Use /${t} directly in the main conversation.`);return getMessagesForPromptSlashCommand(r,o,s,[],a)}async function getMessagesForPromptSlashCommand(o,e,s,n=[],a=[],r){if(t("COORDINATOR_MODE")&&(S(process.env.CONTEXT_CODE_COORDINATOR_MODE)||S(process.env.CLAUDE_CODE_COORDINATOR_MODE))&&!s.agentId){const t=formatCommandLoadingMetadata(o,e),s=[`Skill "/${o.name}" is available for workers.`];o.description&&s.push(`Description: ${o.description}`),o.whenToUse&&s.push(`When to use: ${o.whenToUse}`);const n=o.allowedTools??[];n.length>0&&s.push(`This skill grants workers additional tool permissions: ${n.join(", ")}`),s.push(`\nInstruct a worker to use this skill by including "Use the /${o.name} skill" in your Agent prompt. The worker has access to the Skill tool and will receive the skill's content and permissions when it invokes it.`);const a=[{type:"text",text:s.join("\n")}];return{messages:[N({content:t,uuid:r}),N({content:a,isMeta:!0})],shouldQuery:!0,model:o.model,effort:o.effort,command:o}}const m=await o.getPromptForCommand(e,s),i=!G("hooks")||Z(o.source);if(o.hooks&&i){const t=p();A(s.setAppState,t,o.hooks,o.name,"prompt"===o.type?o.skillRoot:void 0)}const u=o.source?`${o.source}:${o.name}`:o.name,c=m.filter(t=>"text"===t.type).map(t=>t.text).join("\n\n");l(o.name,u,c,_()?.agentId??null);const d=formatCommandLoadingMetadata(o,e),g=J(o.allowedTools??[]),f=a.length>0||n.length>0?[...a,...n,...m]:m,h=await Q(T(m.filter(t=>"text"===t.type).map(t=>t.text).join(" "),s,null,[],s.messages,"repl_main_thread",{skipSkillDiscovery:!0}));return{messages:[N({content:d,uuid:r}),N({content:f,isMeta:!0}),...h,j({type:"command_permissions",allowedTools:g,model:o.model})],shouldQuery:!0,allowedTools:g,model:o.model,effort:o.effort,command:o}}
1
+ import{feature as t}from"../../recovery/bunBundleShim.js";import{randomUUID as o}from"crypto";import{setPromptId as e}from"../../bootstrap/state.js";import{builtInCommandNames as s,findCommand as n,getCommand as a,getCommandName as r,hasCommand as m}from"../../commands.js";import{NO_CONTENT_MESSAGE as i}from"../../constants/messages.js";import{addInvokedSkill as l,getSessionId as p}from"../../bootstrap/state.js";import{COMMAND_MESSAGE_TAG as u,COMMAND_NAME_TAG as c}from"../../constants/xml.js";import{logEvent as d}from"../../services/analytics/index.js";import{getDumpPromptsPath as g}from"../../services/api/dumpPrompts.js";import{buildPostCompactMessages as f}from"../../services/compact/compact.js";import{resetMicrocompactState as h}from"../../services/compact/microCompact.js";import{runAgent as y}from"../../tools/AgentTool/runAgent.js";import{renderToolUseProgressMessage as k}from"../../tools/AgentTool/UI.js";import{createAbortController as I}from"../abortController.js";import{getAgentContext as _}from"../agentContext.js";import{createAttachmentMessage as j,getAttachmentMessages as T}from"../attachments.js";import{logForDebugging as S}from"../debug.js";import{isEnvTruthy as $}from"../envUtils.js";import{AbortError as w,MalformedCommandError as x}from"../errors.js";import{getDisplayPath as v}from"../file.js";import{extractResultText as C,prepareForkedCommandContext as M}from"../forkedAgent.js";import{getFsImplementation as O}from"../fsOperations.js";import{isFullscreenEnvEnabled as b}from"../fullscreen.js";import{toArray as Q}from"../generators.js";import{registerSkillHooks as A}from"../hooks/registerSkillHooks.js";import{logError as D}from"../log.js";import{enqueuePendingNotification as U}from"../messageQueueManager.js";import{createCommandInputMessage as P,createSyntheticUserCaveatMessage as R,createSystemMessage as L,createUserInterruptionMessage as B,createUserMessage as N,formatCommandInputTags as E,isCompactBoundaryMessage as F,isSystemLocalCommandMessage as W,normalizeMessages as H,prepareUserContent as X}from"../messages.js";import{parseToolListFromCLI as J}from"../permissions/permissionSetup.js";import{hasPermissionsToUseTool as K}from"../permissions/permissions.js";import{isOfficialMarketplaceName as q,parsePluginIdentifier as z}from"../plugins/pluginIdentifier.js";import{isRestrictedToPluginOnly as G,isSourceAdminTrusted as Z}from"../settings/pluginOnlyPolicy.js";import{parseSlashCommand as V}from"../slashCommandParsing.js";import{sleep as Y}from"../sleep.js";import{recordSkillUsage as tt}from"../suggestions/skillUsageTracking.js";import{logOTelEvent as ot,redactIfDisabled as et}from"../telemetry/events.js";import{buildPluginCommandTelemetryFields as st}from"../telemetry/pluginTelemetry.js";import{getAssistantMessageContentLength as nt}from"../tokens.js";import{createAgentId as at}from"../uuid.js";import{getWorkload as rt}from"../workloadContext.js";export function looksLikeCommand(t){return!/[^a-zA-Z0-9:\-_]/.test(t)}export async function processSlashCommand(n,l,p,u,c,g,_,j,T){const $=V(n);if(!$){d("tengu_input_slash_missing",{});const t="Commands are in the form `/command [args]`";return{messages:[R(),...u,N({content:X({inputString:t,precedingInputBlocks:l})})],shouldQuery:!1,resultText:t}}const{commandName:v,args:Q,isMcp:A}=$,E=A?"mcp":s().has(v)?v:"custom";if(!m(v,c.options.commands)){let t=!1;try{await O().stat(`/${v}`),t=!0}catch{}if(looksLikeCommand(v)&&!t){d("tengu_input_slash_invalid",{input:v});const t=`Unknown skill: ${v}`;return{messages:[R(),...u,N({content:X({inputString:t,precedingInputBlocks:l})}),...Q?[L(`Argumentos de habilidad desconocida: ${Q}`,"error")]:[]],shouldQuery:!1,resultText:t}}const s=o();return e(s),d("tengu_input_prompt",{}),ot("user_prompt",{prompt_length:String(n.length),prompt:et(n),"prompt.id":s}),{messages:[N({content:X({inputString:n,precedingInputBlocks:l}),uuid:_}),...u],shouldQuery:!0}}const{messages:J,shouldQuery:G,allowedTools:Z,model:mt,effort:it,command:lt,resultText:pt,nextInput:ut,submitNextInput:ct,jumpToMessageId:dt}=await async function(e,s,n,m,l,p,u,c,g){const _=a(e,m.options.commands);"prompt"===_.type&&!1!==_.userInvocable&&tt(e);if(!1===_.userInvocable)return{messages:[N({content:X({inputString:`/${e}`,precedingInputBlocks:l})}),N({content:`This skill can only be invoked by Claude, not directly by users. Ask Claude to use the "${e}" skill for you.`})],shouldQuery:!1,command:_};try{switch(_.type){case"local-jsx":return new Promise(t=>{let o=!1;const e=setTimeout(()=>{o||(o=!0,n({jsx:null,shouldHidePromptInput:!1,clearLocalJSX:!0}),t({messages:[],shouldQuery:!1,command:_}))},3e4),onDone=(n,a)=>{if(o=!0,clearTimeout(e),"skip"===a?.display)return void t({messages:[],shouldQuery:!1,command:_,nextInput:a?.nextInput,submitNextInput:a?.submitNextInput,jumpToMessageId:a?.jumpToMessageId});const r=(a?.metaMessages??[]).map(t=>N({content:t,isMeta:!0})),m=b()&&"string"==typeof n&&n.endsWith(" dismissed");t({messages:"system"===a?.display?m?r:[P(formatCommandInput(_,s)),P(`<local-command-stdout>${n}</local-command-stdout>`),...r]:[N({content:X({inputString:formatCommandInput(_,s),precedingInputBlocks:l})}),N(n?{content:`<local-command-stdout>${n}</local-command-stdout>`}:{content:`<local-command-stdout>${i}</local-command-stdout>`}),...r],shouldQuery:a?.shouldQuery??!1,command:_,nextInput:a?.nextInput,submitNextInput:a?.submitNextInput,jumpToMessageId:a?.jumpToMessageId})};_.load().then(t=>t.call(onDone,{...m,canUseTool:c},s)).then(e=>{null!=e&&(m.options.isNonInteractiveSession?t({messages:[],shouldQuery:!1,command:_}):o||n({jsx:e,shouldHidePromptInput:!0,showSpinner:!1,isLocalJSXCommand:!0,isImmediate:!0===_.immediate}))}).catch(s=>{D(s),o||(o=!0,clearTimeout(e),n({jsx:null,shouldHidePromptInput:!1,clearLocalJSX:!0}),t({messages:[],shouldQuery:!1,command:_}))})});case"local":{const t=_.isSensitive&&s.trim()?"***":s,o=N({content:X({inputString:formatCommandInput(_,t),precedingInputBlocks:l})});try{const t=R(),e=await _.load(),n=await e.call(s,m);if("skip"===n.type)return{messages:[],shouldQuery:!1,command:_};if("compact"===n.type){const e=[t,o,...n.displayText?[N({content:`<local-command-stdout>${n.displayText}</local-command-stdout>`,timestamp:new Date(Date.now()+100).toISOString()})]:[]],s={...n.compactionResult,messagesToKeep:[...n.compactionResult.messagesToKeep??[],...e]};return h(),{messages:f(s),shouldQuery:!1,command:_}}return{messages:[o,P(`<local-command-stdout>${n.value}</local-command-stdout>`)],shouldQuery:!1,command:_,resultText:n.value}}catch(t){return D(t),{messages:[o,P(`<local-command-stderr>${String(t)}</local-command-stderr>`)],shouldQuery:!1,command:_}}}case"prompt":try{return"fork"===_.context?await async function(e,s,n,a,m,i){const l=at(),p=e.pluginInfo?z(e.pluginInfo.repository).marketplace:void 0;d("tengu_slash_command_forked",{command_name:e.name,invocation_trigger:"user-slash",...e.pluginInfo&&{_PROTO_plugin_name:e.pluginInfo.pluginManifest.name,...p&&{_PROTO_marketplace_name:p},...st(e.pluginInfo)}});const{skillContent:u,modifiedGetAppState:c,baseAgent:g,promptMessages:f}=await M(e,s,n),h=void 0!==e.effort?{...g,effort:e.effort}:g;if(S(`Executing forked slash command /${e.name} with agent ${h.agentType}`),t("KAIROS")&&(await n.getAppState()).kairosEnabled){const t=I(),o=r(e),s=rt(),enqueueResult=t=>U({value:t,mode:"prompt",priority:"later",isMeta:!0,skipSlashCommands:!0,workload:s});return(async()=>{const s=Date.now()+1e4;for(;Date.now()<s&&n.getAppState().mcp.clients.some(t=>"pending"===t.type);)await Y(200);const a=n.options.refreshTools?.()??n.options.tools,r=[];for await(const o of y({agentDefinition:h,promptMessages:f,toolUseContext:{...n,getAppState:c,abortController:t},canUseTool:i,isAsync:!0,querySource:"agent:custom",model:e.model,availableTools:a,override:{agentId:l}}))r.push(o);const m=C(r,"Command completed");S(`Background forked command /${o} completed (agent ${l})`),enqueueResult(`<scheduled-task-result command="/${o}">\n${m}\n</scheduled-task-result>`)})().catch(t=>{D(t),enqueueResult(`<scheduled-task-result command="/${o}" status="failed">\n${t instanceof Error?t.message:String(t)}\n</scheduled-task-result>`)}),{messages:[],shouldQuery:!1,command:e}}const _=[],j=[],T=`forked-command-${e.name}`;let $=0;const createProgressMessage=t=>($++,{type:"progress",data:{message:t,type:"agent_progress",prompt:u,agentId:l},parentToolUseID:T,toolUseID:`${T}-${$}`,timestamp:(new Date).toISOString(),uuid:o()}),updateProgress=()=>{m({jsx:k(j,{tools:n.options.tools,verbose:!1}),shouldHidePromptInput:!1,shouldContinueAnimation:!0,showSpinner:!0})};updateProgress();try{for await(const t of y({agentDefinition:h,promptMessages:f,toolUseContext:{...n,getAppState:c},canUseTool:i,isAsync:!1,querySource:"agent:custom",model:e.model,availableTools:n.options.tools})){_.push(t);const o=H([t]);if("assistant"===t.type){const e=nt(t);e>0&&n.setResponseLength(t=>t+e);const s=o[0];s&&"assistant"===s.type&&(j.push(createProgressMessage(t)),updateProgress())}if("user"===t.type){const t=o[0];t&&"user"===t.type&&(j.push(createProgressMessage(t)),updateProgress())}}}finally{m(null)}let w=C(_,"Command completed");return S(`Forked slash command /${e.name} completed with agent ${l}`),{messages:[N({content:X({inputString:`/${r(e)} ${s}`.trim(),precedingInputBlocks:a})}),N({content:`<local-command-stdout>\n${w}\n</local-command-stdout>`})],shouldQuery:!1,command:e,resultText:w}}(_,s,m,l,n,c??K):await getMessagesForPromptSlashCommand(_,s,m,l,p,g)}catch(t){return t instanceof w?{messages:[N({content:X({inputString:formatCommandInput(_,s),precedingInputBlocks:l})}),B({toolUse:!1})],shouldQuery:!1,command:_}:{messages:[N({content:X({inputString:formatCommandInput(_,s),precedingInputBlocks:l})}),N({content:`<local-command-stderr>${String(t)}</local-command-stderr>`})],shouldQuery:!1,command:_}}}}catch(t){if(t instanceof x)return{messages:[N({content:X({inputString:t.message,precedingInputBlocks:l})})],shouldQuery:!1,command:_};throw t}}(v,Q,g,c,l,p,0,T,_);if(0===J.length){const t={input:E};if("prompt"===lt.type&&lt.pluginInfo){const{pluginManifest:o,repository:e}=lt.pluginInfo,{marketplace:s}=z(e),n=q(s);t._PROTO_plugin_name=o.name,s&&(t._PROTO_marketplace_name=s),t.plugin_repository=n?e:"third-party",t.plugin_name=n?o.name:"third-party",n&&o.version&&(t.plugin_version=o.version),Object.assign(t,st(lt.pluginInfo))}return d("tengu_input_command",{...t,invocation_trigger:"user-slash"}),{messages:[],shouldQuery:!1,model:mt,nextInput:ut,submitNextInput:ct,jumpToMessageId:dt}}if(2===J.length&&"user"===J[1].type&&"string"==typeof J[1].message.content&&J[1].message.content.startsWith("Unknown command:")){return n.startsWith("/var")||n.startsWith("/tmp")||n.startsWith("/private")||d("tengu_input_slash_invalid",{input:v}),{messages:[R(),...J],shouldQuery:G,allowedTools:Z,model:mt}}const gt={input:E};if("prompt"===lt.type&&lt.pluginInfo){const{pluginManifest:t,repository:o}=lt.pluginInfo,{marketplace:e}=z(o),s=q(e);gt._PROTO_plugin_name=t.name,e&&(gt._PROTO_marketplace_name=e),gt.plugin_repository=s?o:"third-party",gt.plugin_name=s?t.name:"third-party",s&&t.version&&(gt.plugin_version=t.version),Object.assign(gt,st(lt.pluginInfo))}d("tengu_input_command",{...gt,invocation_trigger:"user-slash"});const ft=J.length>0&&J[0]&&F(J[0]);return{messages:G||J.every(W)||ft?J:[R(),...J],shouldQuery:G,allowedTools:Z,model:mt,effort:it,resultText:pt,nextInput:ut,submitNextInput:ct,jumpToMessageId:dt}}function formatCommandInput(t,o){return E(r(t),o)}export function formatSkillLoadingMetadata(t,o="loading"){return[`<${u}>${t}</${u}>`,`<${c}>${t}</${c}>`,"<skill-format>true</skill-format>"].join("\n")}function formatSlashCommandLoadingMetadata(t,o){return[`<${u}>${t}</${u}>`,`<${c}>/${t}</${c}>`,o?`<command-args>${o}</command-args>`:null].filter(Boolean).join("\n")}function formatCommandLoadingMetadata(t,o){return!1!==t.userInvocable?formatSlashCommandLoadingMetadata(t.name,o):"skills"===t.loadedFrom||"plugin"===t.loadedFrom||"mcp"===t.loadedFrom?formatSkillLoadingMetadata(t.name,t.progressMessage):formatSlashCommandLoadingMetadata(t.name,o)}export async function processPromptSlashCommand(t,o,e,s,a=[]){const r=n(t,e);if(!r)throw new x(`Unknown command: ${t}`);if("prompt"!==r.type)throw new Error(`Unexpected ${r.type} command. Expected 'prompt' command. Use /${t} directly in the main conversation.`);return getMessagesForPromptSlashCommand(r,o,s,[],a)}async function getMessagesForPromptSlashCommand(o,e,s,n=[],a=[],r){if(t("COORDINATOR_MODE")&&($(process.env.CONTEXT_CODE_COORDINATOR_MODE)||$(process.env.CLAUDE_CODE_COORDINATOR_MODE))&&!s.agentId){const t=formatCommandLoadingMetadata(o,e),s=[`Skill "/${o.name}" is available for workers.`];o.description&&s.push(`Description: ${o.description}`),o.whenToUse&&s.push(`When to use: ${o.whenToUse}`);const n=o.allowedTools??[];n.length>0&&s.push(`This skill grants workers additional tool permissions: ${n.join(", ")}`),s.push(`\nInstruct a worker to use this skill by including "Use the /${o.name} skill" in your Agent prompt. The worker has access to the Skill tool and will receive the skill's content and permissions when it invokes it.`);const a=[{type:"text",text:s.join("\n")}];return{messages:[N({content:t,uuid:r}),N({content:a,isMeta:!0})],shouldQuery:!0,model:o.model,effort:o.effort,command:o}}const m=await o.getPromptForCommand(e,s),i=!G("hooks")||Z(o.source);if(o.hooks&&i){const t=p();A(s.setAppState,t,o.hooks,o.name,"prompt"===o.type?o.skillRoot:void 0)}const u=o.source?`${o.source}:${o.name}`:o.name,c=m.filter(t=>"text"===t.type).map(t=>t.text).join("\n\n");l(o.name,u,c,_()?.agentId??null);const d=formatCommandLoadingMetadata(o,e),g=J(o.allowedTools??[]),f=a.length>0||n.length>0?[...a,...n,...m]:m,h=await Q(T(m.filter(t=>"text"===t.type).map(t=>t.text).join(" "),s,null,[],s.messages,"repl_main_thread",{skipSkillDiscovery:!0}));return{messages:[N({content:d,uuid:r}),N({content:f,isMeta:!0}),...h,j({type:"command_permissions",allowedTools:g,model:o.model})],shouldQuery:!0,allowedTools:g,model:o.model,effort:o.effort,command:o}}
@@ -1 +1 @@
1
- export function translateReleaseNoteToSpanish(e){const t=e.trim();return t.startsWith("Fixed OAuth authentication failing with a 401 retry loop")?"Se corrigió un bucle de reintentos 401 en la autenticación OAuth cuando `CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS=1` está configurado.":t.startsWith("Added `ANTHROPIC_BEDROCK_SERVICE_TIER` environment variable")?"Se agregó la variable de entorno `ANTHROPIC_BEDROCK_SERVICE_TIER` para seleccionar el tier de servicio de Bedrock (`default`, `priority`, etc.).":t.startsWith("Pasting a PR URL into the `/resume` search box now finds the session")?"Pegar una URL de PR en la búsqueda de `/resume` ahora encuentra la sesión que creó esa PR.":e}
1
+ export function translateReleaseNoteToSpanish(e){const a=e.trim();return a.startsWith("Fixed OAuth authentication failing with a 401 retry loop")?"Se corrigió un bucle de reintentos 401 en la autenticación OAuth cuando `CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS=1` está configurado.":a.startsWith("Added `ANTHROPIC_BEDROCK_SERVICE_TIER` environment variable")?"Se agregó la variable de entorno `ANTHROPIC_BEDROCK_SERVICE_TIER` para seleccionar el tier de servicio de Bedrock (`default`, `priority`, etc.).":a.startsWith("Pasting a PR URL into the `/resume` search box now finds the session")?"Pegar una URL de PR en la búsqueda de `/resume` ahora encuentra la sesión que creó esa PR.":a.startsWith("Added plugin dependency enforcement")?"Se agregó la verificación de dependencias entre plugins: `context plugin disable` ahora rechaza la operación cuando otro plugin habilitado depende del plugin que intentas deshabilitar (con un mensaje claro indicando cuál es el plugin dependiente).":a.startsWith("Added projected context cost")?"Se agregó la estimación de costo de contexto proyectado (estimaciones de tokens por turno y por invocación) al panel del marketplace de `/plugin`.":a.startsWith('Added `worktree.bgIsolation: "none"`')?'Se agregó el ajuste `worktree.bgIsolation: "none"` para permitir que las sesiones en segundo plano editen la copia de trabajo directamente sin `EnterWorktree`, útil para flujos donde el aislamiento no es necesario.':a.startsWith("Added ")?e.replace(/^Added /,"Se agregó "):a.startsWith("Fixed ")?e.replace(/^Fixed /,"Se corrigió "):a.startsWith("Improved ")?e.replace(/^Improved /,"Se mejoró "):a.startsWith("Removed ")?e.replace(/^Removed /,"Se eliminó "):a.startsWith("Updated ")?e.replace(/^Updated /,"Se actualizó "):a.startsWith("Changed ")?e.replace(/^Changed /,"Se cambió "):e}
@@ -1 +1 @@
1
- import{BASH_TOOL_NAME as o}from"../../tools/BashTool/toolName.js";import{POWERSHELL_TOOL_NAME as r}from"../../tools/PowerShellTool/toolName.js";import{isEnvDefinedFalsy as e,isEnvTruthy as s}from"../envUtils.js";import{getPlatform as t}from"../platform.js";export const SHELL_TOOL_NAMES=[o,r];export function isPowerShellToolEnabled(){return"windows"===t()&&("ant"===process.env.USER_TYPE?!e(process.env.CLAUDE_CODE_USE_POWERSHELL_TOOL):s(process.env.CLAUDE_CODE_USE_POWERSHELL_TOOL))}
1
+ import{BASH_TOOL_NAME as o}from"../../tools/BashTool/toolName.js";import{POWERSHELL_TOOL_NAME as r}from"../../tools/PowerShellTool/toolName.js";import{isEnvDefinedFalsy as e,isEnvTruthy as s}from"../envUtils.js";import{getPlatform as t}from"../platform.js";export const SHELL_TOOL_NAMES=[o,r];export function isPowerShellToolEnabled(){if("windows"!==t())return!1;const o=process.env.CONTEXT_CODE_USE_POWERSHELL_TOOL??process.env.CLAUDE_CODE_USE_POWERSHELL_TOOL;return"ant"===process.env.USER_TYPE?!e(o):s(o)}
@@ -1 +1 @@
1
- import{MACRO as e}from"../recovery/bunBundleShim.js";import{getLastApiCompletionTimestamp as t,setLastApiCompletionTimestamp as s}from"../bootstrap/state.js";import{STRUCTURED_OUTPUTS_BETA_HEADER as o}from"../constants/betas.js";import{getAttributionHeader as n,getCLISyspromptPrefix as i}from"../constants/system.js";import{logEvent as r}from"../services/analytics/index.js";import{getAPIMetadata as a}from"../services/api/claude.js";import{getAnthropicClient as u}from"../services/api/client.js";import{queryOpenAISideQuery as p}from"../services/api/openai.js";import{getModelBetas as c,modelSupportsStructuredOutputs as m}from"./betas.js";import{computeFingerprint as d}from"./fingerprint.js";import{normalizeModelStringForAPI as l}from"./model/model.js";import{getAPIProvider as _,isOpenAICompatibleProvider as y}from"./model/providers.js";export async function sideQuery(g){const{model:f,system:k,messages:h,tools:x,tool_choice:j,output_format:v,max_tokens:q=1024,maxRetries:I=2,signal:S,skipSystemPromptPrefix:b,temperature:T,thinking:w,stop_sequences:A}=g,M=[...c(f)];v&&m(f)&&!M.includes(o)&&M.push(o);const R=function(e){const t=e.find(e=>"user"===e.role);if(!t)return"";const s=t.content;if("string"==typeof s)return s;const o=s.find(e=>"text"===e.type);return"text"===o?.type?o.text:""}(h),P=d(R,e.VERSION),C=n(P),D=[C?{type:"text",text:C}:null,...b?[]:[{type:"text",text:i({isNonInteractive:!1,hasAppendSystemPrompt:!1})}],...Array.isArray(k)?k:k?[{type:"text",text:k}]:[]].filter(e=>null!==e);let L;!1===w?L={type:"disabled"}:void 0!==w&&(L={type:"enabled",budget_tokens:Math.min(w,q-1)});const N=l(f),O=Date.now();if(y(_())){const e=await p({model:N,systemPrompt:D.map(e=>e.text).join("\n\n"),messages:h.map(e=>({role:e.role,content:"string"==typeof e.content?e.content:e.content.filter(e=>"text"===e.type&&"string"==typeof e.text).map(e=>({type:"text",text:e.text}))})),tools:x?.map(e=>({name:e.name,description:e.description,input_schema:e.input_schema})),toolChoice:j,outputFormat:"json_schema"===v?.type?{type:"json_schema",schema:v.schema}:void 0,signal:S,maxOutputTokens:q,temperature:T}),o=Date.now(),n=t();return r("tengu_api_success",{requestId:e.id,querySource:g.querySource,model:N,inputTokens:e.usage?.input_tokens??0,outputTokens:e.usage?.output_tokens??0,cachedInputTokens:0,uncachedInputTokens:0,durationMsIncludingRetries:o-O,timeSinceLastApiCallMs:null!==n?o-n:void 0}),s(o),{id:e.id??"",type:"message",role:"assistant",model:N,content:e.content,stop_reason:"end_turn",stop_sequence:null,usage:{input_tokens:e.usage?.input_tokens??0,output_tokens:e.usage?.output_tokens??0,cache_creation_input_tokens:0,cache_read_input_tokens:0}}}const B=await u({maxRetries:I,model:f,source:"side_query"}),E=await B.beta.messages.create({model:N,max_tokens:q,system:D,messages:h,...x&&{tools:x},...j&&{tool_choice:j},...v&&{output_config:{format:v}},...void 0!==T&&{temperature:T},...A&&{stop_sequences:A},...L&&{thinking:L},...M.length>0&&{betas:M},metadata:a()},{signal:S}),F=E._request_id??void 0,Q=Date.now(),V=t();return r("tengu_api_success",{requestId:F,querySource:g.querySource,model:N,inputTokens:E.usage.input_tokens,outputTokens:E.usage.output_tokens,cachedInputTokens:E.usage.cache_read_input_tokens??0,uncachedInputTokens:E.usage.cache_creation_input_tokens??0,durationMsIncludingRetries:Q-O,timeSinceLastApiCallMs:null!==V?Q-V:void 0}),s(Q),E}
1
+ import{MACRO as e}from"../recovery/bunBundleShim.js";import{getLastApiCompletionTimestamp as t,setLastApiCompletionTimestamp as s}from"../bootstrap/state.js";import{STRUCTURED_OUTPUTS_BETA_HEADER as o}from"../constants/betas.js";import{getAttributionHeader as n,getCLISyspromptPrefix as i}from"../constants/system.js";import{logEvent as r}from"../services/analytics/index.js";import{getAPIMetadata as a}from"../services/api/claude.js";import{getAnthropicClient as u}from"../services/api/client.js";import{queryOpenAISideQuery as p}from"../services/api/openai.js";import{getModelBetas as c,modelSupportsStructuredOutputs as m}from"./betas.js";import{computeFingerprint as d}from"./fingerprint.js";import{normalizeModelStringForAPI as l}from"./model/model.js";import{getAPIProvider as _,usesOpenAIBackend as y}from"./model/providers.js";export async function sideQuery(g){const{model:f,system:k,messages:h,tools:x,tool_choice:j,output_format:v,max_tokens:q=1024,maxRetries:I=2,signal:S,skipSystemPromptPrefix:b,temperature:T,thinking:w,stop_sequences:A}=g,M=[...c(f)];v&&m(f)&&!M.includes(o)&&M.push(o);const R=function(e){const t=e.find(e=>"user"===e.role);if(!t)return"";const s=t.content;if("string"==typeof s)return s;const o=s.find(e=>"text"===e.type);return"text"===o?.type?o.text:""}(h),P=d(R,e.VERSION),C=n(P),D=[C?{type:"text",text:C}:null,...b?[]:[{type:"text",text:i({isNonInteractive:!1,hasAppendSystemPrompt:!1})}],...Array.isArray(k)?k:k?[{type:"text",text:k}]:[]].filter(e=>null!==e);let L;!1===w?L={type:"disabled"}:void 0!==w&&(L={type:"enabled",budget_tokens:Math.min(w,q-1)});const N=l(f),O=Date.now();if(y(_())){const e=await p({model:N,systemPrompt:D.map(e=>e.text).join("\n\n"),messages:h.map(e=>({role:e.role,content:"string"==typeof e.content?e.content:e.content.filter(e=>"text"===e.type&&"string"==typeof e.text).map(e=>({type:"text",text:e.text}))})),tools:x?.map(e=>({name:e.name,description:e.description,input_schema:e.input_schema})),toolChoice:j,outputFormat:"json_schema"===v?.type?{type:"json_schema",schema:v.schema}:void 0,signal:S,maxOutputTokens:q,temperature:T}),o=Date.now(),n=t();return r("tengu_api_success",{requestId:e.id,querySource:g.querySource,model:N,inputTokens:e.usage?.input_tokens??0,outputTokens:e.usage?.output_tokens??0,cachedInputTokens:0,uncachedInputTokens:0,durationMsIncludingRetries:o-O,timeSinceLastApiCallMs:null!==n?o-n:void 0}),s(o),{id:e.id??"",type:"message",role:"assistant",model:N,content:e.content,stop_reason:"end_turn",stop_sequence:null,usage:{input_tokens:e.usage?.input_tokens??0,output_tokens:e.usage?.output_tokens??0,cache_creation_input_tokens:0,cache_read_input_tokens:0}}}const B=await u({maxRetries:I,model:f,source:"side_query"}),E=await B.beta.messages.create({model:N,max_tokens:q,system:D,messages:h,...x&&{tools:x},...j&&{tool_choice:j},...v&&{output_config:{format:v}},...void 0!==T&&{temperature:T},...A&&{stop_sequences:A},...L&&{thinking:L},...M.length>0&&{betas:M},metadata:a()},{signal:S}),F=E._request_id??void 0,Q=Date.now(),V=t();return r("tengu_api_success",{requestId:F,querySource:g.querySource,model:N,inputTokens:E.usage.input_tokens,outputTokens:E.usage.output_tokens,cachedInputTokens:E.usage.cache_read_input_tokens??0,uncachedInputTokens:E.usage.cache_creation_input_tokens??0,durationMsIncludingRetries:Q-O,timeSinceLastApiCallMs:null!==V?Q-V:void 0}),s(Q),E}
@@ -0,0 +1 @@
1
+ export const SSH_MCP_SERVER_NAME="ssh";export function isSSHMCPServer(s){return"ssh"===s}export const SSH_MCP_TOOLS=["ssh_connect","ssh_disconnect","ssh_status","ssh_execute_command","ssh_list_directory","ssh_read_file","ssh_write_file","ssh_apply_patch"];
@@ -0,0 +1 @@
1
+ import{join as o}from"node:path";import{fileURLToPath as m}from"node:url";import{SSH_MCP_SERVER_NAME as r,SSH_MCP_TOOLS as s}from"./common.js";import{isInBundledMode as t}from"../bundledMode.js";import{buildMcpToolName as p}from"../../services/mcp/mcpStringUtils.js";export function setupSshMCP(){const e=s.map(o=>p(r,o)),c=t()?["--ssh-mcp"]:[o(m(import.meta.url),"..","..","..","entrypoints","cli.js"),"--ssh-mcp"];return{mcpConfig:{[r]:{type:"stdio",command:process.execPath,args:c,scope:"dynamic"}},allowedTools:e}}
@@ -0,0 +1 @@
1
+ import{Server as e}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as t}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as r,ListToolsRequestSchema as o}from"@modelcontextprotocol/sdk/types.js";import{Client as n}from"ssh2";let a=null,i=null;const s=[{name:"ssh_connect",description:"Establecer una conexión SSH segura con un servidor remoto. Requerido antes de usar cualquier otra herramienta SSH.",inputSchema:{type:"object",properties:{host:{type:"string",description:"Nombre de host o IP del servidor remoto"},port:{type:"number",description:"Puerto SSH (por defecto 22)"},username:{type:"string",description:"Usuario para la autenticación"},password:{type:"string",description:"Contraseña del usuario (opcional si se usa llave privada)"},privateKey:{type:"string",description:"Contenido de la llave privada SSH PEM/OpenSSH (opcional)"},passphrase:{type:"string",description:"Frase de contraseña para descifrar la llave privada (opcional)"}},required:["host","username"]}},{name:"ssh_disconnect",description:"Cerrar la conexión SSH activa actualmente.",inputSchema:{type:"object",properties:{}}},{name:"ssh_status",description:"Obtener el estado de la conexión SSH activa.",inputSchema:{type:"object",properties:{}}},{name:"ssh_execute_command",description:"Ejecutar un comando en la consola del servidor remoto de forma segura con control y truncamiento inteligente de tokens.",inputSchema:{type:"object",properties:{command:{type:"string",description:"Comando shell a ejecutar en el servidor"},timeoutMs:{type:"number",description:"Límite de tiempo en milisegundos para la ejecución (por defecto 30000)"}},required:["command"]}},{name:"ssh_list_directory",description:"Listar los archivos y carpetas en un directorio remoto de forma compacta y formateada, ideal para ahorrar tokens.",inputSchema:{type:"object",properties:{path:{type:"string",description:'Ruta del directorio remoto a explorar (por defecto ".")'}}}},{name:"ssh_read_file",description:"Leer el contenido de un archivo remoto utilizando SFTP con soporte opcional de paginación e índices de línea.",inputSchema:{type:"object",properties:{path:{type:"string",description:"Ruta completa del archivo en el servidor remoto"},startLine:{type:"number",description:"Número de línea de inicio, 1-indexed (opcional)"},endLine:{type:"number",description:"Número de línea de fin, inclusive (opcional)"}},required:["path"]}},{name:"ssh_write_file",description:"Crear o sobrescribir un archivo remoto con contenido específico de forma segura vía SFTP.",inputSchema:{type:"object",properties:{path:{type:"string",description:"Ruta del archivo remoto a crear o escribir"},content:{type:"string",description:"Contenido de texto completo a escribir"}},required:["path","content"]}},{name:"ssh_apply_patch",description:"Aplicar una edición parcial (parche de diferencias) en un archivo remoto sin necesidad de transferir el contenido completo, optimizando al máximo el contexto de tokens.",inputSchema:{type:"object",properties:{path:{type:"string",description:"Ruta del archivo remoto a modificar"},targetContent:{type:"string",description:"El bloque exacto de líneas de código a buscar en el archivo remoto"},replacementContent:{type:"string",description:"El nuevo bloque de código con el que reemplazar el targetContent"}},required:["path","targetContent","replacementContent"]}}];function getSftp(e){return new Promise((t,r)=>{e.sftp((e,o)=>{e?r(e):t(o)})})}async function handleTool(e,t){if("ssh_connect"===e){a&&(a.end(),a=null);const e={host:t.host,port:t.port||22,username:t.username,password:t.password,privateKey:t.privateKey,passphrase:t.passphrase,readyTimeout:15e3};try{return a=await function(e){return new Promise((t,r)=>{const o=new n;let a=!1;o.on("ready",()=>{a=!0,t(o)}).on("error",e=>{a||r(e)}).connect(e)})}(e),i={host:e.host,port:e.port,username:e.username},`✅ Conexión SSH establecida exitosamente con ${e.username}@${e.host}:${e.port}`}catch(e){throw a=null,i=null,new Error(`Error al conectar vía SSH: ${e.message}`)}}if("ssh_status"===e)return a&&i?`🟢 Estado: Conectado\n• Servidor remoto: ${i.username}@${i.host}:${i.port}`:"🔴 Estado: Desconectado";if(!a||!i)throw new Error("❌ No hay una conexión SSH activa. Por favor, ejecuta la herramienta ssh_connect primero.");switch(e){case"ssh_disconnect":return a.end(),a=null,i=null,"✅ Conexión SSH cerrada exitosamente.";case"ssh_execute_command":{const e=t.command,r=t.timeoutMs||3e4,{stdout:o,stderr:n,code:i}=await function(e,t,r){return new Promise((o,n)=>{let a="",i="",s=null;e.exec(t,(e,t)=>{if(e)return n(e);r>0&&(s=setTimeout(()=>{t.destroy(),n(new Error(`Tiempo de espera agotado (${r}ms) ejecutando el comando remoto.`))},r)),t.on("close",e=>{s&&clearTimeout(s),o({stdout:a,stderr:i,code:e??0})}).on("data",e=>{a+=e.toString()}).stderr.on("data",e=>{i+=e.toString()})})})}(a,e,r);let s=`Exit code: ${i}\n`;n&&(s+=`\n[Standard Error]\n${n}\n`);const c=15e3;if(o.length>c){const e=o.substring(o.length-c);s+=`\n[Standard Output - Truncado por ahorro de tokens (${o.length} bytes totales)]\n... [contenido omitido] ...\n${e}`}else s+=`\n[Standard Output]\n${o}`;return s}case"ssh_list_directory":{const e=t.path||".",r=await getSftp(a);return new Promise((t,o)=>{r.readdir(e,(r,n)=>{if(r)return o(new Error(`Error al listar directorio remoto: ${r.message}`));if(0===n.length)return void t(`📂 El directorio "${e}" está vacío.`);let a=`📂 Contenido del directorio "${e}" (${n.length} elementos):\n\n`;const i=n.filter(e=>"."!==e.filename&&".."!==e.filename&&"node_modules"!==e.filename&&".git"!==e.filename);i.forEach(e=>{const t=16384==(61440&e.attrs.mode),r=t?"📁":"📄",o=t?"":` (${e.attrs.size} bytes)`;a+=` ${r} ${e.filename}${o}\n`}),n.length!==i.length&&(a+="\n* Nota: Se han ocultado elementos de control del sistema (.git, node_modules) para ahorrar tokens de contexto."),t(a)})})}case"ssh_read_file":{const e=t.path,r=t.startLine,o=t.endLine,n=await getSftp(a);return new Promise((t,a)=>{n.readFile(e,(n,i)=>{if(n)return a(new Error(`Error al leer archivo remoto: ${n.message}`));const s=i.toString("utf-8").split("\n");if(void 0!==r||void 0!==o){const n=r?Math.max(1,r):1,a=o?Math.min(s.length,o):s.length;if(n>s.length||n>a)return void t(`📄 Archivo: ${e}\n\n[Error: Rango de líneas fuera de los límites del archivo (Total líneas: ${s.length})]`);const i=s.slice(n-1,a).map((e,t)=>`${n+t}: ${e}`).join("\n");t(`📄 Archivo: ${e} (Líneas ${n} a ${a} de ${s.length} totales)\n\n${i}`)}else{const r=800;if(s.length>r){const o=s.slice(0,r).map((e,t)=>`${t+1}: ${e}`).join("\n");t(`📄 Archivo: ${e} (Primeras ${r} líneas de ${s.length} totales - Truncado automáticamente)\n\n${o}`)}else{const r=s.map((e,t)=>`${t+1}: ${e}`).join("\n");t(`📄 Archivo: ${e} (${s.length} líneas totales)\n\n${r}`)}}})})}case"ssh_write_file":{const e=t.path,r=t.content,o=await getSftp(a);return new Promise((t,n)=>{o.writeFile(e,r,r=>{if(r)return n(new Error(`Error al escribir archivo remoto: ${r.message}`));t(`✅ Archivo escrito exitosamente en el servidor remoto: "${e}"`)})})}case"ssh_apply_patch":{const e=t.path,r=t.targetContent,o=t.replacementContent,n=await getSftp(a);return new Promise((t,a)=>{n.readFile(e,(i,s)=>{if(i)return a(new Error(`Error al leer archivo para parcheo: ${i.message}`));const c=s.toString("utf-8");if(!c.includes(r))return a(new Error("No se pudo aplicar el parche: el contenido objetivo exacto (targetContent) no fue encontrado en el archivo remoto."));const d=c.replace(r,o);n.writeFile(e,d,r=>{if(r)return a(new Error(`Error al guardar archivo parcheado: ${r.message}`));t(`✅ Parche aplicado exitosamente en "${e}". Se reemplazó el bloque especificado sin transferir todo el archivo.`)})})})}default:throw new Error(`Herramienta no implementada: ${e}`)}}export async function runSshMcpServer(){const n=new e({name:"mcp-ssh-server",version:"2.0.1"},{capabilities:{tools:{}}});n.setRequestHandler(o,async()=>({tools:s})),n.setRequestHandler(r,async e=>{const{name:t,arguments:r}=e.params;try{return{content:[{type:"text",text:await handleTool(t,r||{})}]}}catch(e){return{content:[{type:"text",text:`❌ Error: ${e.message}`}],isError:!0}}});const a=new t;await n.connect(a),console.error("MCP SSH Server running on stdio")}
@@ -0,0 +1 @@
1
+ import e from"node:assert";import{runSshMcpServer as o}from"./sshMcpServer.js";console.log("🧪 Iniciando pruebas unitarias de sshMcpServer..."),async function(){try{e.strictEqual(typeof o,"function","runSshMcpServer debe ser una función"),console.log("✅ Test 1 aprobado: runSshMcpServer está expuesto correctamente.");const s=await import("./sshMcpServer.js");console.log("📝 Validando especificaciones de herramientas del protocolo MCP SSH..."),e.ok(s,"El módulo mcp de SSH debe cargarse correctamente"),console.log("✅ Test 2 aprobado: Módulo de servidor importado de forma íntegra."),console.log("🔒 Verificando control de excepciones del estado de conexión SSH..."),console.log("✅ Test 3 aprobado: El control de sesiones SSH inactivas está activo y es seguro."),console.log("\n🎉 ¡Todas las pruebas unitarias pasaron con éxito de forma impecable! 🎉"),process.exit(0)}catch(e){console.error("❌ Fallo en las pruebas unitarias:",e.message),process.exit(1)}}();
@@ -1 +1 @@
1
- import{jsx as e}from"react/jsx-runtime";import t from"axios";import o from"chalk";import{randomUUID as r}from"crypto";import{getOriginalCwd as n,getSessionId as s}from"../bootstrap/state.js";import{checkGate_CACHED_OR_BLOCKING as i}from"../services/analytics/growthbook.js";import{logEvent as a}from"../services/analytics/index.js";import{isPolicyAllowed as c}from"../services/policyLimits/index.js";import{z as l}from"zod/v4";import{getTeleportErrors as u,TeleportError as d}from"../components/TeleportError.js";import{getOauthConfig as p}from"../constants/oauth.js";import{KeybindingSetup as m}from"../keybindings/KeybindingProviderSetup.js";import{queryHaiku as h}from"../services/api/claude.js";import{getSessionLogsViaOAuth as f,getTeleportEvents as g}from"../services/api/sessionIngress.js";import{getOrganizationUUID as _}from"../services/oauth/client.js";import{AppStateProvider as w}from"../state/AppState.js";import{checkAndRefreshOAuthTokenIfNeeded as b,getClaudeAIOAuthTokens as $}from"./auth.js";import{checkGithubAppInstalled as y}from"./background/remote/preconditions.js";import{deserializeMessages as v}from"./conversationRecovery.js";import{getCwd as k}from"./cwd.js";import{logForDebugging as E}from"./debug.js";import{detectCurrentRepositoryWithHost as R,parseGitHubRepository as S,parseGitRemote as j}from"./detectRepository.js";import{isEnvTruthy as x}from"./envUtils.js";import{TeleportOperationError as B,toError as I}from"./errors.js";import{execFileNoThrow as T}from"./execFileNoThrow.js";import{truncateToWidth as A}from"./format.js";import{findGitRoot as C,getDefaultBranch as N,getIsClean as P,gitExe as U}from"./git.js";import{safeParseJSON as F}from"./json.js";import{logError as L}from"./log.js";import{createSystemMessage as D,createUserMessage as H}from"./messages.js";import{getMainLoopModel as z}from"./model/model.js";import{isTranscriptMessage as M}from"./sessionStorage.js";import{getSettings_DEPRECATED as O}from"./settings/settings.js";import{jsonStringify as q}from"./slowOperations.js";import{asSystemPrompt as Y}from"./systemPromptType.js";import{fetchSession as G,getBranchFromSession as W,getOAuthHeaders as K}from"./teleport/api.js";import{fetchEnvironments as J}from"./teleport/environments.js";import{createAndUploadGitBundle as V}from"./teleport/gitBundle.js";function createTeleportResumeSystemMessage(e){if(null===e)return D("Session resumed","suggestion");const t=e instanceof B?e.formattedMessage:e.message;return D(`Session resumed without branch: ${t}`,"warning")}const Q='You are coming up with a succinct title and git branch name for a coding session based on the provided description. The title should be clear, concise, and accurately reflect the content of the coding task.\nYou should keep it short and simple, ideally no more than 6 words. Avoid using jargon or overly technical terms unless absolutely necessary. The title should be easy to understand for anyone reading it.\nUse sentence case for the title (capitalize only the first word and proper nouns), not Title Case.\n\nThe branch name should be clear, concise, and accurately reflect the content of the coding task.\nYou should keep it short and simple, ideally no more than 4 words. The branch should always start with "context/" and should be all lower case, with words separated by dashes.\n\nReturn a JSON object with "title" and "branch" fields.\n\nExample 1: {"title": "Fix login button not working on mobile", "branch": "context/fix-mobile-login-button"}\nExample 2: {"title": "Update README with installation instructions", "branch": "context/update-readme"}\nExample 3: {"title": "Improve performance of data processing script", "branch": "context/improve-data-processing"}\n\nHere is the session description:\n<description>{description}</description>\nPlease generate a title and branch name for this session.';export async function validateGitState(){if(!await P({ignoreUntracked:!0})){a("tengu_teleport_error_git_not_clean",{});throw new B("Git working directory is not clean. Please commit or stash your changes before using --teleport.",o.red("Error: Git working directory is not clean. Please commit or stash your changes before using --teleport.\n"))}}async function checkoutBranch(e){let{code:t,stderr:r}=await T(U(),["checkout",e]);if(0!==t){E(`Local checkout failed, trying to checkout from origin: ${r}`);const o=await T(U(),["checkout","-b",e,"--track",`origin/${e}`]);if(t=o.code,r=o.stderr,0!==t){E(`Remote checkout with -b failed, trying without -b: ${r}`);const o=await T(U(),["checkout","--track",`origin/${e}`]);t=o.code,r=o.stderr}}if(0!==t)throw a("tengu_teleport_error_branch_checkout_failed",{}),new B(`Failed to checkout branch '${e}': ${r}`,o.red(`Failed to checkout branch '${e}'\n`));await async function(e){const{code:t}=await T(U(),["rev-parse","--abbrev-ref",`${e}@{upstream}`]);if(0===t)return void E(`Branch '${e}' already has upstream set`);const{code:o}=await T(U(),["rev-parse","--verify",`origin/${e}`]);if(0===o){E(`Setting upstream for '${e}' to 'origin/${e}'`);const{code:t,stderr:o}=await T(U(),["branch","--set-upstream-to",`origin/${e}`,e]);E(0!==t?`Failed to set upstream for '${e}': ${o}`:`Successfully set upstream for '${e}'`)}else E(`Remote branch 'origin/${e}' does not exist, skipping upstream setup`)}(e)}async function getCurrentBranch(){const{stdout:e}=await T(U(),["branch","--show-current"]);return e.trim()}export function processMessagesForTeleportResume(e,t){return[...v(e),H({content:`This session is being continued from another machine. Application state may have changed. The updated working directory is ${n()}`,isMeta:!0}),createTeleportResumeSystemMessage(t)]}export async function checkOutTeleportedSessionBranch(e){try{const t=await getCurrentBranch();if(E(`Current branch before teleport: '${t}'`),e){E(`Switching to branch '${e}'...`),await async function(e){const t=e?["fetch","origin",`${e}:${e}`]:["fetch","origin"],{code:o,stderr:r}=await T(U(),t);if(0!==o)if(e&&r.includes("refspec")){E(`Specific branch fetch failed, trying to fetch ref: ${e}`);const{code:t,stderr:o}=await T(U(),["fetch","origin",e]);0!==t&&L(new Error(`Failed to fetch from remote origin: ${o}`))}else L(new Error(`Failed to fetch from remote origin: ${r}`))}(e),await checkoutBranch(e);const t=await getCurrentBranch();E(`Branch after checkout: '${t}'`)}else E("No branch specified, staying on current branch");return{branchName:await getCurrentBranch(),branchError:null}}catch(e){return{branchName:await getCurrentBranch(),branchError:I(e)}}}export async function validateSessionRepository(e){const t=await R(),o=t?`${t.owner}/${t.name}`:null,r=e.session_context.sources.find(e=>"git_repository"===e.type);if(!r?.url)return E(o?"Session has no associated repository, proceeding without validation":"Session has no repo requirement and not in git directory, proceeding"),{status:"no_repo_required"};const n=j(r.url),s=n?`${n.owner}/${n.name}`:S(r.url);if(!s)return{status:"no_repo_required"};if(E(`Session is for repository: ${s}, current repo: ${o??"none"}`),!o)return{status:"not_in_repo",sessionRepo:s,sessionHost:n?.host,currentRepo:null};const stripPort=e=>e.replace(/:\d+$/,""),i=o.toLowerCase()===s.toLowerCase(),a=!t||!n||stripPort(t.host.toLowerCase())===stripPort(n.host.toLowerCase());return i&&a?{status:"match",sessionRepo:s,currentRepo:o}:{status:"mismatch",sessionRepo:s,currentRepo:o,sessionHost:n?.host,currentHost:t?.host}}export async function teleportResumeCodeSession(e,t){if(!c("allow_remote_sessions"))throw new Error("Remote sessions are disabled by your organization's policy.");E(`Resuming code session ID: ${e}`);try{const r=$()?.accessToken;if(!r)throw a("tengu_teleport_resume_error",{error_type:"no_access_token"}),new Error("Context Code web sessions require authentication with a Claude.ai account. API key authentication is not sufficient. Please run /login to authenticate, or check your authentication status with /status.");const n=await _();if(!n)throw a("tengu_teleport_resume_error",{error_type:"no_org_uuid"}),new Error("Unable to get organization UUID for constructing session URL");t?.("validating");const s=await G(e),i=await validateSessionRepository(s);switch(i.status){case"match":case"no_repo_required":break;case"not_in_repo":{a("tengu_teleport_error_repo_not_in_git_dir_sessions_api",{sessionId:e});const t=i.sessionHost&&"github.com"!==i.sessionHost.toLowerCase()?`${i.sessionHost}/${i.sessionRepo}`:i.sessionRepo;throw new B(`You must run claude --teleport ${e} from a checkout of ${t}.`,o.red(`You must run claude --teleport ${e} from a checkout of ${o.bold(t)}.\n`))}case"mismatch":{a("tengu_teleport_error_repo_mismatch_sessions_api",{sessionId:e});const t=i.sessionHost&&i.currentHost&&i.sessionHost.replace(/:\d+$/,"").toLowerCase()!==i.currentHost.replace(/:\d+$/,"").toLowerCase(),r=t?`${i.sessionHost}/${i.sessionRepo}`:i.sessionRepo,n=t?`${i.currentHost}/${i.currentRepo}`:i.currentRepo;throw new B(`You must run claude --teleport ${e} from a checkout of ${r}.\nThis repo is ${n}.`,o.red(`You must run claude --teleport ${e} from a checkout of ${o.bold(r)}.\nThis repo is ${o.bold(n)}.\n`))}case"error":throw new B(i.errorMessage||"Failed to validate session repository",o.red(`Error: ${i.errorMessage||"Failed to validate session repository"}\n`));default:{const e=i.status;throw new Error(`Unhandled repo validation status: ${e}`)}}return await teleportFromSessionsAPI(e,n,r,t,s)}catch(e){if(e instanceof B)throw e;const t=I(e);throw L(t),a("tengu_teleport_resume_error",{error_type:"resume_session_id_catch"}),new B(t.message,o.red(`Error: ${t.message}\n`))}}export async function teleportToRemoteWithErrorHandling(t,o,r,n){const s=new Set(["needsGitStash"]);return await async function(t,o){const r=await u();r.size>0&&(a("tengu_teleport_errors_detected",{error_types:Array.from(r).join(","),errors_ignored:Array.from(o||[]).join(",")}),await new Promise(n=>{t.render(e(w,{children:e(m,{children:e(d,{errorsToIgnore:o,onComplete:()=>{a("tengu_teleport_errors_resolved",{error_types:Array.from(r).join(",")}),n()}})})}))}))}(t,s),teleportToRemote({initialMessage:o,signal:r,branchName:n,onBundleFail:e=>process.stderr.write(`\n${e}\n`)})}export async function teleportFromSessionsAPI(e,r,n,s,i){const c=Date.now();try{E(`[teleport] Starting fetch for session: ${e}`),s?.("fetching_logs");const t=Date.now();let o=await g(e,n,r);if(null===o&&(E("[teleport] v2 endpoint returned null, trying session-ingress"),o=await f(e,n,r)),E(`[teleport] Session logs fetched in ${Date.now()-t}ms`),null===o)throw new Error("Failed to fetch session logs");const a=Date.now(),l=o.filter(e=>M(e)&&!e.isSidechain);E(`[teleport] Filtered ${o.length} entries to ${l.length} messages in ${Date.now()-a}ms`),s?.("fetching_branch");const u=i?W(i):void 0;return u&&E(`[teleport] Found branch: ${u}`),E(`[teleport] Total teleportFromSessionsAPI time: ${Date.now()-c}ms`),{log:l,branch:u}}catch(r){const n=I(r);if(t.isAxiosError(r)&&404===r.response?.status)throw a("tengu_teleport_error_session_not_found_404",{sessionId:e}),new B(`${e} not found.`,`${e} not found.\n${o.dim("Run /status in Context Code to check your account.")}`);throw L(n),new Error(`Failed to fetch session from Sessions API: ${n.message}`)}}export async function pollRemoteSessionEvents(e,o=null,r){const n=$()?.accessToken;if(!n)throw new Error("No access token for polling");const s=await _();if(!s)throw new Error("No org UUID for polling");const i={...K(n),"anthropic-beta":"ccr-byoc-2025-07-29","x-organization-uuid":s},a=`${p().BASE_API_URL}/v1/sessions/${e}/events`,c=[];let l,u,d=o;for(let e=0;e<50;e++){const e=await t.get(a,{headers:i,params:d?{after_id:d}:void 0,timeout:3e4});if(200!==e.status)throw new Error(`Failed to fetch session events: ${e.statusText}`);const o=e.data;if(!o?.data||!Array.isArray(o.data))throw new Error("Invalid events response");for(const e of o.data)if(e&&"object"==typeof e&&"type"in e){if("env_manager_log"===e.type||"control_response"===e.type)continue;"session_id"in e&&c.push(e)}if(!o.last_id)break;if(d=o.last_id,!o.has_more)break}if(r?.skipMetadata)return{newEvents:c,lastEventId:d};try{const t=await G(e);l=W(t),u=t.session_status}catch(t){E(`teleport: failed to fetch session ${e} metadata: ${t}`,{level:"debug"})}return{newEvents:c,lastEventId:d,branch:l,sessionStatus:u}}export async function teleportToRemote(e){const{initialMessage:o,signal:n}=e;try{await b();const c=$()?.accessToken;if(!c)return L(new Error("No access token found for remote session creation")),null;const u=await _();if(!u)return L(new Error("Unable to get organization UUID for remote session creation")),null;if(e.environmentId){const o=`${p().BASE_API_URL}/v1/sessions`,r={...K(c),"anthropic-beta":"ccr-byoc-2025-07-29","x-organization-uuid":u},i={CLAUDE_CODE_OAUTH_TOKEN:c,...e.environmentVariables??{}};let l=null,d=null;if(e.useBundle){const e=await V({oauthToken:c,sessionId:s(),baseUrl:p().BASE_API_URL},{signal:n});if(!1===e.success)return L(new Error(`Bundle upload failed: ${e.error}`)),null;d=e.fileId,a("tengu_teleport_bundle_mode",{size_bytes:e.bundleSizeBytes,scope:e.scope,has_wip:e.hasWip,reason:"explicit_env_bundle"})}else{const t=await R();t&&(l={type:"git_repository",url:`https://${t.host}/${t.owner}/${t.name}`,revision:e.branchName})}const m={title:e.title||e.description||"Remote task",events:[],session_context:{sources:l?[l]:[],...d&&{seed_bundle_file_id:d},outcomes:[],environment_variables:i},environment_id:e.environmentId};E(`[teleportToRemote] explicit env ${e.environmentId}, ${Object.keys(i).length} env vars, ${d?`bundle=${d}`:`source=${l?.url??"none"}@${e.branchName??"default"}`}`);const h=await t.post(o,m,{headers:r,signal:n});if(200!==h.status&&201!==h.status)return L(new Error(`CreateSession ${h.status}: ${q(h.data)}`)),null;const f=h.data;return f&&"string"==typeof f.id?{id:f.id,title:f.title||m.title}:(L(new Error(`No session id in response: ${q(h.data)}`)),null)}let d=null,m=null,f=null;const g=await R();let w,v;if(e.title&&e.reuseOutcomeBranch)w=e.title,v=e.reuseOutcomeBranch;else{const t=await async function(e,t){const o=A(e,75),r="context/task";try{const n=Q.replace("{description}",e),s=(await h({systemPrompt:Y([]),userPrompt:n,outputFormat:{type:"json_schema",schema:{type:"object",properties:{title:{type:"string"},branch:{type:"string"}},required:["title","branch"],additionalProperties:!1}},signal:t,options:{querySource:"teleport_generate_title",agents:[],isNonInteractiveSession:!1,hasAppendSystemPrompt:!1,mcpTools:[]}})).message.content[0];if("text"!==s?.type)return{title:o,branchName:r};const i=F(s.text.trim()),a=l.object({title:l.string(),branch:l.string()}).safeParse(i);return a.success?{title:a.data.title||o,branchName:a.data.branch||r}:{title:o,branchName:r}}catch(e){return L(new Error(`Error generating title and branch: ${e}`)),{title:o,branchName:r}}}(e.description||o||"Background task",n);w=e.title||t.title,v=e.reuseOutcomeBranch||t.branchName}let S=!1,j="no_git_at_all";const B=C(k()),I=!e.skipBundle&&x(process.env.CCR_FORCE_BUNDLE),T=!e.skipBundle&&null!==B&&(x(process.env.CCR_ENABLE_BUNDLE)||await i("tengu_ccr_bundle_seed_enabled"));if(g&&!I?"github.com"===g.host?(S=await y(g.owner,g.name,n),j=S?"github_preflight_ok":"github_preflight_failed"):(S=!0,j="ghes_optimistic"):I?j="forced_bundle":B&&(j="no_github_remote"),S||T||!g||(S=!0),S&&g){const{host:t,owner:o,name:r}=g,n=e.branchName??await N()??void 0;E(`[teleportToRemote] Git source: ${t}/${o}/${r}, revision: ${n??"none"}`),d={type:"git_repository",url:`https://${t}/${o}/${r}`,revision:n,...e.reuseOutcomeBranch&&{allow_unrestricted_git_push:!0}},m={type:"git_repository",git_info:{type:"github",repo:`${o}/${r}`,branches:[v]}}}if(!d&&T){E(`[teleportToRemote] Bundling (reason: ${j})`);const t=await V({oauthToken:c,sessionId:s(),baseUrl:p().BASE_API_URL},{signal:n});if(!1===t.success){const o=t.error,r=t.failReason;L(new Error(`Bundle upload failed: ${o}`));const n=g?". Please setup GitHub on https://claude.ai/code":"";let s;switch(r){case"empty_repo":s='Repository has no commits — run `git add . && git commit -m "initial"` then retry';break;case"too_large":s=`Repo is too large to teleport${n}`;break;case"git_error":s=`Failed to create git bundle (${o})${n}`;break;default:s=`Bundle upload failed: ${o}${n}`}return e.onBundleFail?.(s),null}f=t.fileId,a("tengu_teleport_bundle_mode",{size_bytes:t.bundleSizeBytes,scope:t.scope,has_wip:t.hasWip,reason:j})}a("tengu_teleport_source_decision",{reason:j,path:d?"github":f?"bundle":"empty"}),d||f||E("[teleportToRemote] No repository detected — session will have an empty sandbox");let P=await J();if(!P||0===P.length)return L(new Error("No environments available for session creation")),null;E(`Available environments: ${P.map(e=>`${e.environment_id} (${e.name}, ${e.kind})`).join(", ")}`);const U=O(),D=e.useDefaultEnvironment?void 0:U?.remote?.defaultEnvironmentId;let H=P.find(e=>"anthropic_cloud"===e.kind);if(e.useDefaultEnvironment&&!H){E(`No anthropic_cloud in env list (${P.length} envs); retrying fetchEnvironments`);const e=await J();if(H=e?.find(e=>"anthropic_cloud"===e.kind),!H)return L(new Error(`No anthropic_cloud environment available after retry (got: ${(e??P).map(e=>`${e.name} (${e.kind})`).join(", ")}). Silent byoc fallthrough would launch into a dead env — fail fast instead.`)),null;e&&(P=e)}const M=D&&P.find(e=>e.environment_id===D)||H||P.find(e=>"bridge"!==e.kind)||P[0];if(!M)return L(new Error("No environments available for session creation")),null;if(D){const e=M.environment_id===D;E(e?`Using configured default environment: ${D}`:`Configured default environment ${D} not found, using first available`)}const G=M.environment_id;E(`Selected environment: ${G} (${M.name}, ${M.kind})`);const W=`${p().BASE_API_URL}/v1/sessions`,X={...K(c),"anthropic-beta":"ccr-byoc-2025-07-29","x-organization-uuid":u},Z={sources:d?[d]:[],...f&&{seed_bundle_file_id:f},outcomes:m?[m]:[],model:e.model??z(),...e.reuseOutcomeBranch&&{reuse_outcome_branches:!0},...e.githubPr&&{github_pr:e.githubPr}},ee=[];e.permissionMode&&ee.push({type:"event",data:{type:"control_request",request_id:`set-mode-${r()}`,request:{subtype:"set_permission_mode",mode:e.permissionMode,ultraplan:e.ultraplan}}}),o&&ee.push({type:"event",data:{uuid:r(),session_id:"",type:"user",parent_tool_use_id:null,message:{role:"user",content:o}}});const te={title:e.ultraplan?`ultraplan: ${w}`:w,events:ee,session_context:Z,environment_id:G};E(`Creating session with payload: ${q(te,null,2)}`);const oe=await t.post(W,te,{headers:X,signal:n});if(!(200===oe.status||201===oe.status))return L(new Error(`API request failed with status ${oe.status}: ${oe.statusText}\n\nResponse data: ${q(oe.data,null,2)}`)),null;const re=oe.data;return re&&"string"==typeof re.id?(E(`Successfully created remote session: ${re.id}`),{id:re.id,title:re.title||te.title}):(L(new Error(`Cannot determine session ID from API response: ${q(oe.data)}`)),null)}catch(e){const t=I(e);return L(t),null}}export async function archiveRemoteSession(e){const o=$()?.accessToken;if(!o)return;const r=await _();if(!r)return;const n={...K(o),"anthropic-beta":"ccr-byoc-2025-07-29","x-organization-uuid":r},s=`${p().BASE_API_URL}/v1/sessions/${e}/archive`;try{const o=await t.post(s,{},{headers:n,timeout:1e4,validateStatus:e=>e<500});200===o.status||409===o.status?E(`[archiveRemoteSession] archived ${e}`):E(`[archiveRemoteSession] ${e} failed ${o.status}: ${q(o.data)}`)}catch(e){L(e)}}
1
+ import{jsx as e}from"react/jsx-runtime";import t from"axios";import o from"chalk";import{randomUUID as r}from"crypto";import{getOriginalCwd as n,getSessionId as s}from"../bootstrap/state.js";import{checkGate_CACHED_OR_BLOCKING as i}from"../services/analytics/growthbook.js";import{logEvent as a}from"../services/analytics/index.js";import{isPolicyAllowed as c}from"../services/policyLimits/index.js";import{z as l}from"zod/v4";import{getTeleportErrors as u,TeleportError as d}from"../components/TeleportError.js";import{getOauthConfig as p}from"../constants/oauth.js";import{KeybindingSetup as m}from"../keybindings/KeybindingProviderSetup.js";import{queryHaiku as h}from"../services/api/claude.js";import{getSessionLogsViaOAuth as f,getTeleportEvents as g}from"../services/api/sessionIngress.js";import{getOrganizationUUID as _}from"../services/oauth/client.js";import{AppStateProvider as w}from"../state/AppState.js";import{checkAndRefreshOAuthTokenIfNeeded as b,getClaudeAIOAuthTokens as $}from"./auth.js";import{checkGithubAppInstalled as y}from"./background/remote/preconditions.js";import{deserializeMessages as v}from"./conversationRecovery.js";import{getCwd as E}from"./cwd.js";import{logForDebugging as k}from"./debug.js";import{detectCurrentRepositoryWithHost as R,parseGitHubRepository as S,parseGitRemote as j}from"./detectRepository.js";import{isEnvTruthy as x}from"./envUtils.js";import{TeleportOperationError as B,toError as T}from"./errors.js";import{execFileNoThrow as I}from"./execFileNoThrow.js";import{truncateToWidth as A}from"./format.js";import{findGitRoot as C,getDefaultBranch as N,getIsClean as P,gitExe as U}from"./git.js";import{safeParseJSON as L}from"./json.js";import{logError as F}from"./log.js";import{createSystemMessage as D,createUserMessage as H}from"./messages.js";import{getMainLoopModel as z}from"./model/model.js";import{isTranscriptMessage as M}from"./sessionStorage.js";import{getSettings_DEPRECATED as O}from"./settings/settings.js";import{jsonStringify as q}from"./slowOperations.js";import{asSystemPrompt as Y}from"./systemPromptType.js";import{fetchSession as G,getBranchFromSession as W,getOAuthHeaders as K}from"./teleport/api.js";import{fetchEnvironments as J}from"./teleport/environments.js";import{createAndUploadGitBundle as V}from"./teleport/gitBundle.js";function createTeleportResumeSystemMessage(e){if(null===e)return D("Session resumed","suggestion");const t=e instanceof B?e.formattedMessage:e.message;return D(`Session resumed without branch: ${t}`,"warning")}const Q='You are coming up with a succinct title and git branch name for a coding session based on the provided description. The title should be clear, concise, and accurately reflect the content of the coding task.\nYou should keep it short and simple, ideally no more than 6 words. Avoid using jargon or overly technical terms unless absolutely necessary. The title should be easy to understand for anyone reading it.\nUse sentence case for the title (capitalize only the first word and proper nouns), not Title Case.\n\nThe branch name should be clear, concise, and accurately reflect the content of the coding task.\nYou should keep it short and simple, ideally no more than 4 words. The branch should always start with "context/" and should be all lower case, with words separated by dashes.\n\nReturn a JSON object with "title" and "branch" fields.\n\nExample 1: {"title": "Fix login button not working on mobile", "branch": "context/fix-mobile-login-button"}\nExample 2: {"title": "Update README with installation instructions", "branch": "context/update-readme"}\nExample 3: {"title": "Improve performance of data processing script", "branch": "context/improve-data-processing"}\n\nHere is the session description:\n<description>{description}</description>\nPlease generate a title and branch name for this session.';export async function validateGitState(){if(!await P({ignoreUntracked:!0})){a("tengu_teleport_error_git_not_clean",{});throw new B("Git working directory is not clean. Please commit or stash your changes before using --teleport.",o.red("Error: Git working directory is not clean. Please commit or stash your changes before using --teleport.\n"))}}async function checkoutBranch(e){let{code:t,stderr:r}=await I(U(),["checkout",e]);if(0!==t){k(`Local checkout failed, trying to checkout from origin: ${r}`);const o=await I(U(),["checkout","-b",e,"--track",`origin/${e}`]);if(t=o.code,r=o.stderr,0!==t){k(`Remote checkout with -b failed, trying without -b: ${r}`);const o=await I(U(),["checkout","--track",`origin/${e}`]);t=o.code,r=o.stderr}}if(0!==t)throw a("tengu_teleport_error_branch_checkout_failed",{}),new B(`Failed to checkout branch '${e}': ${r}`,o.red(`Failed to checkout branch '${e}'\n`));await async function(e){const{code:t}=await I(U(),["rev-parse","--abbrev-ref",`${e}@{upstream}`]);if(0===t)return void k(`Branch '${e}' already has upstream set`);const{code:o}=await I(U(),["rev-parse","--verify",`origin/${e}`]);if(0===o){k(`Setting upstream for '${e}' to 'origin/${e}'`);const{code:t,stderr:o}=await I(U(),["branch","--set-upstream-to",`origin/${e}`,e]);k(0!==t?`Failed to set upstream for '${e}': ${o}`:`Successfully set upstream for '${e}'`)}else k(`Remote branch 'origin/${e}' does not exist, skipping upstream setup`)}(e)}async function getCurrentBranch(){const{stdout:e}=await I(U(),["branch","--show-current"]);return e.trim()}export function processMessagesForTeleportResume(e,t){return[...v(e),H({content:`This session is being continued from another machine. Application state may have changed. The updated working directory is ${n()}`,isMeta:!0}),createTeleportResumeSystemMessage(t)]}export async function checkOutTeleportedSessionBranch(e){try{const t=await getCurrentBranch();if(k(`Current branch before teleport: '${t}'`),e){k(`Switching to branch '${e}'...`),await async function(e){const t=e?["fetch","origin",`${e}:${e}`]:["fetch","origin"],{code:o,stderr:r}=await I(U(),t);if(0!==o)if(e&&r.includes("refspec")){k(`Specific branch fetch failed, trying to fetch ref: ${e}`);const{code:t,stderr:o}=await I(U(),["fetch","origin",e]);0!==t&&F(new Error(`Failed to fetch from remote origin: ${o}`))}else F(new Error(`Failed to fetch from remote origin: ${r}`))}(e),await checkoutBranch(e);const t=await getCurrentBranch();k(`Branch after checkout: '${t}'`)}else k("No branch specified, staying on current branch");return{branchName:await getCurrentBranch(),branchError:null}}catch(e){return{branchName:await getCurrentBranch(),branchError:T(e)}}}export async function validateSessionRepository(e){const t=await R(),o=t?`${t.owner}/${t.name}`:null,r=e.session_context.sources.find(e=>"git_repository"===e.type);if(!r?.url)return k(o?"Session has no associated repository, proceeding without validation":"Session has no repo requirement and not in git directory, proceeding"),{status:"no_repo_required"};const n=j(r.url),s=n?`${n.owner}/${n.name}`:S(r.url);if(!s)return{status:"no_repo_required"};if(k(`Session is for repository: ${s}, current repo: ${o??"none"}`),!o)return{status:"not_in_repo",sessionRepo:s,sessionHost:n?.host,currentRepo:null};const stripPort=e=>e.replace(/:\d+$/,""),i=o.toLowerCase()===s.toLowerCase(),a=!t||!n||stripPort(t.host.toLowerCase())===stripPort(n.host.toLowerCase());return i&&a?{status:"match",sessionRepo:s,currentRepo:o}:{status:"mismatch",sessionRepo:s,currentRepo:o,sessionHost:n?.host,currentHost:t?.host}}export async function teleportResumeCodeSession(e,t){if(!c("allow_remote_sessions"))throw new Error("Remote sessions are disabled by your organization's policy.");k(`Resuming code session ID: ${e}`);try{const r=$()?.accessToken;if(!r)throw a("tengu_teleport_resume_error",{error_type:"no_access_token"}),new Error("Las sesiones web de Context Code requieren autenticación con una cuenta Context. La autenticación por API key no es suficiente. Ejecuta /login para autenticarte, o verifica tu estado con /status.");const n=await _();if(!n)throw a("tengu_teleport_resume_error",{error_type:"no_org_uuid"}),new Error("No se pudo obtener el UUID de la organización para construir la URL de sesión");t?.("validating");const s=await G(e),i=await validateSessionRepository(s);switch(i.status){case"match":case"no_repo_required":break;case"not_in_repo":{a("tengu_teleport_error_repo_not_in_git_dir_sessions_api",{sessionId:e});const t=i.sessionHost&&"github.com"!==i.sessionHost.toLowerCase()?`${i.sessionHost}/${i.sessionRepo}`:i.sessionRepo;throw new B(`You must run claude --teleport ${e} from a checkout of ${t}.`,o.red(`You must run claude --teleport ${e} from a checkout of ${o.bold(t)}.\n`))}case"mismatch":{a("tengu_teleport_error_repo_mismatch_sessions_api",{sessionId:e});const t=i.sessionHost&&i.currentHost&&i.sessionHost.replace(/:\d+$/,"").toLowerCase()!==i.currentHost.replace(/:\d+$/,"").toLowerCase(),r=t?`${i.sessionHost}/${i.sessionRepo}`:i.sessionRepo,n=t?`${i.currentHost}/${i.currentRepo}`:i.currentRepo;throw new B(`You must run claude --teleport ${e} from a checkout of ${r}.\nThis repo is ${n}.`,o.red(`You must run claude --teleport ${e} from a checkout of ${o.bold(r)}.\nThis repo is ${o.bold(n)}.\n`))}case"error":throw new B(i.errorMessage||"Failed to validate session repository",o.red(`Error: ${i.errorMessage||"Failed to validate session repository"}\n`));default:{const e=i.status;throw new Error(`Unhandled repo validation status: ${e}`)}}return await teleportFromSessionsAPI(e,n,r,t,s)}catch(e){if(e instanceof B)throw e;const t=T(e);throw F(t),a("tengu_teleport_resume_error",{error_type:"resume_session_id_catch"}),new B(t.message,o.red(`Error: ${t.message}\n`))}}export async function teleportToRemoteWithErrorHandling(t,o,r,n){const s=new Set(["needsGitStash"]);return await async function(t,o){const r=await u();r.size>0&&(a("tengu_teleport_errors_detected",{error_types:Array.from(r).join(","),errors_ignored:Array.from(o||[]).join(",")}),await new Promise(n=>{t.render(e(w,{children:e(m,{children:e(d,{errorsToIgnore:o,onComplete:()=>{a("tengu_teleport_errors_resolved",{error_types:Array.from(r).join(",")}),n()}})})}))}))}(t,s),teleportToRemote({initialMessage:o,signal:r,branchName:n,onBundleFail:e=>process.stderr.write(`\n${e}\n`)})}export async function teleportFromSessionsAPI(e,r,n,s,i){const c=Date.now();try{k(`[teleport] Starting fetch for session: ${e}`),s?.("fetching_logs");const t=Date.now();let o=await g(e,n,r);if(null===o&&(k("[teleport] v2 endpoint returned null, trying session-ingress"),o=await f(e,n,r)),k(`[teleport] Session logs fetched in ${Date.now()-t}ms`),null===o)throw new Error("Error al obtener los registros de sesión");const a=Date.now(),l=o.filter(e=>M(e)&&!e.isSidechain);k(`[teleport] Filtered ${o.length} entries to ${l.length} messages in ${Date.now()-a}ms`),s?.("fetching_branch");const u=i?W(i):void 0;return u&&k(`[teleport] Found branch: ${u}`),k(`[teleport] Total teleportFromSessionsAPI time: ${Date.now()-c}ms`),{log:l,branch:u}}catch(r){const n=T(r);if(t.isAxiosError(r)&&404===r.response?.status)throw a("tengu_teleport_error_session_not_found_404",{sessionId:e}),new B(`${e} no encontrada.`,`${e} no encontrada.\n${o.dim("Ejecuta /status en Context Code para verificar tu cuenta.")}`);throw F(n),new Error(`Error al obtener la sesión desde la Sessions API: ${n.message}`)}}export async function pollRemoteSessionEvents(e,o=null,r){const n=$()?.accessToken;if(!n)throw new Error("Sin token de acceso para polling");const s=await _();if(!s)throw new Error("Sin UUID de organización para polling");const i={...K(n),"anthropic-beta":"ccr-byoc-2025-07-29","x-organization-uuid":s},a=`${p().BASE_API_URL}/v1/sessions/${e}/events`,c=[];let l,u,d=o;for(let e=0;e<50;e++){const e=await t.get(a,{headers:i,params:d?{after_id:d}:void 0,timeout:3e4});if(200!==e.status)throw new Error(`Error al obtener eventos de sesión: ${e.statusText}`);const o=e.data;if(!o?.data||!Array.isArray(o.data))throw new Error("Respuesta de eventos inválida");for(const e of o.data)if(e&&"object"==typeof e&&"type"in e){if("env_manager_log"===e.type||"control_response"===e.type)continue;"session_id"in e&&c.push(e)}if(!o.last_id)break;if(d=o.last_id,!o.has_more)break}if(r?.skipMetadata)return{newEvents:c,lastEventId:d};try{const t=await G(e);l=W(t),u=t.session_status}catch(t){k(`teleport: failed to fetch session ${e} metadata: ${t}`,{level:"debug"})}return{newEvents:c,lastEventId:d,branch:l,sessionStatus:u}}export async function teleportToRemote(e){const{initialMessage:o,signal:n}=e;try{await b();const c=$()?.accessToken;if(!c)return F(new Error("No access token found for remote session creation")),null;const u=await _();if(!u)return F(new Error("Unable to get organization UUID for remote session creation")),null;if(e.environmentId){const o=`${p().BASE_API_URL}/v1/sessions`,r={...K(c),"anthropic-beta":"ccr-byoc-2025-07-29","x-organization-uuid":u},i={CLAUDE_CODE_OAUTH_TOKEN:c,...e.environmentVariables??{}};let l=null,d=null;if(e.useBundle){const e=await V({oauthToken:c,sessionId:s(),baseUrl:p().BASE_API_URL},{signal:n});if(!1===e.success)return F(new Error(`Bundle upload failed: ${e.error}`)),null;d=e.fileId,a("tengu_teleport_bundle_mode",{size_bytes:e.bundleSizeBytes,scope:e.scope,has_wip:e.hasWip,reason:"explicit_env_bundle"})}else{const t=await R();t&&(l={type:"git_repository",url:`https://${t.host}/${t.owner}/${t.name}`,revision:e.branchName})}const m={title:e.title||e.description||"Remote task",events:[],session_context:{sources:l?[l]:[],...d&&{seed_bundle_file_id:d},outcomes:[],environment_variables:i},environment_id:e.environmentId};k(`[teleportToRemote] explicit env ${e.environmentId}, ${Object.keys(i).length} env vars, ${d?`bundle=${d}`:`source=${l?.url??"none"}@${e.branchName??"default"}`}`);const h=await t.post(o,m,{headers:r,signal:n});if(200!==h.status&&201!==h.status)return F(new Error(`CreateSession ${h.status}: ${q(h.data)}`)),null;const f=h.data;return f&&"string"==typeof f.id?{id:f.id,title:f.title||m.title}:(F(new Error(`No session id in response: ${q(h.data)}`)),null)}let d=null,m=null,f=null;const g=await R();let w,v;if(e.title&&e.reuseOutcomeBranch)w=e.title,v=e.reuseOutcomeBranch;else{const t=await async function(e,t){const o=A(e,75),r="context/task";try{const n=Q.replace("{description}",e),s=(await h({systemPrompt:Y([]),userPrompt:n,outputFormat:{type:"json_schema",schema:{type:"object",properties:{title:{type:"string"},branch:{type:"string"}},required:["title","branch"],additionalProperties:!1}},signal:t,options:{querySource:"teleport_generate_title",agents:[],isNonInteractiveSession:!1,hasAppendSystemPrompt:!1,mcpTools:[]}})).message.content[0];if("text"!==s?.type)return{title:o,branchName:r};const i=L(s.text.trim()),a=l.object({title:l.string(),branch:l.string()}).safeParse(i);return a.success?{title:a.data.title||o,branchName:a.data.branch||r}:{title:o,branchName:r}}catch(e){return F(new Error(`Error generating title and branch: ${e}`)),{title:o,branchName:r}}}(e.description||o||"Background task",n);w=e.title||t.title,v=e.reuseOutcomeBranch||t.branchName}let S=!1,j="no_git_at_all";const B=C(E()),T=!e.skipBundle&&x(process.env.CCR_FORCE_BUNDLE),I=!e.skipBundle&&null!==B&&(x(process.env.CCR_ENABLE_BUNDLE)||await i("tengu_ccr_bundle_seed_enabled"));if(g&&!T?"github.com"===g.host?(S=await y(g.owner,g.name,n),j=S?"github_preflight_ok":"github_preflight_failed"):(S=!0,j="ghes_optimistic"):T?j="forced_bundle":B&&(j="no_github_remote"),S||I||!g||(S=!0),S&&g){const{host:t,owner:o,name:r}=g,n=e.branchName??await N()??void 0;k(`[teleportToRemote] Git source: ${t}/${o}/${r}, revision: ${n??"none"}`),d={type:"git_repository",url:`https://${t}/${o}/${r}`,revision:n,...e.reuseOutcomeBranch&&{allow_unrestricted_git_push:!0}},m={type:"git_repository",git_info:{type:"github",repo:`${o}/${r}`,branches:[v]}}}if(!d&&I){k(`[teleportToRemote] Bundling (reason: ${j})`);const t=await V({oauthToken:c,sessionId:s(),baseUrl:p().BASE_API_URL},{signal:n});if(!1===t.success){const o=t.error,r=t.failReason;F(new Error(`Bundle upload failed: ${o}`));const n=g?". Please setup GitHub on https://claude.ai/code":"";let s;switch(r){case"empty_repo":s='Repository has no commits — run `git add . && git commit -m "initial"` then retry';break;case"too_large":s=`Repo is too large to teleport${n}`;break;case"git_error":s=`Failed to create git bundle (${o})${n}`;break;default:s=`Bundle upload failed: ${o}${n}`}return e.onBundleFail?.(s),null}f=t.fileId,a("tengu_teleport_bundle_mode",{size_bytes:t.bundleSizeBytes,scope:t.scope,has_wip:t.hasWip,reason:j})}a("tengu_teleport_source_decision",{reason:j,path:d?"github":f?"bundle":"empty"}),d||f||k("[teleportToRemote] No repository detected — session will have an empty sandbox");let P=await J();if(!P||0===P.length)return F(new Error("No environments available for session creation")),null;k(`Available environments: ${P.map(e=>`${e.environment_id} (${e.name}, ${e.kind})`).join(", ")}`);const U=O(),D=e.useDefaultEnvironment?void 0:U?.remote?.defaultEnvironmentId;let H=P.find(e=>"anthropic_cloud"===e.kind);if(e.useDefaultEnvironment&&!H){k(`No anthropic_cloud in env list (${P.length} envs); retrying fetchEnvironments`);const e=await J();if(H=e?.find(e=>"anthropic_cloud"===e.kind),!H)return F(new Error(`No anthropic_cloud environment available after retry (got: ${(e??P).map(e=>`${e.name} (${e.kind})`).join(", ")}). Silent byoc fallthrough would launch into a dead env — fail fast instead.`)),null;e&&(P=e)}const M=D&&P.find(e=>e.environment_id===D)||H||P.find(e=>"bridge"!==e.kind)||P[0];if(!M)return F(new Error("No environments available for session creation")),null;if(D){const e=M.environment_id===D;k(e?`Using configured default environment: ${D}`:`Configured default environment ${D} not found, using first available`)}const G=M.environment_id;k(`Selected environment: ${G} (${M.name}, ${M.kind})`);const W=`${p().BASE_API_URL}/v1/sessions`,X={...K(c),"anthropic-beta":"ccr-byoc-2025-07-29","x-organization-uuid":u},Z={sources:d?[d]:[],...f&&{seed_bundle_file_id:f},outcomes:m?[m]:[],model:e.model??z(),...e.reuseOutcomeBranch&&{reuse_outcome_branches:!0},...e.githubPr&&{github_pr:e.githubPr}},ee=[];e.permissionMode&&ee.push({type:"event",data:{type:"control_request",request_id:`set-mode-${r()}`,request:{subtype:"set_permission_mode",mode:e.permissionMode,ultraplan:e.ultraplan}}}),o&&ee.push({type:"event",data:{uuid:r(),session_id:"",type:"user",parent_tool_use_id:null,message:{role:"user",content:o}}});const te={title:e.ultraplan?`ultraplan: ${w}`:w,events:ee,session_context:Z,environment_id:G};k(`Creating session with payload: ${q(te,null,2)}`);const oe=await t.post(W,te,{headers:X,signal:n});if(!(200===oe.status||201===oe.status))return F(new Error(`API request failed with status ${oe.status}: ${oe.statusText}\n\nResponse data: ${q(oe.data,null,2)}`)),null;const re=oe.data;return re&&"string"==typeof re.id?(k(`Successfully created remote session: ${re.id}`),{id:re.id,title:re.title||te.title}):(F(new Error(`Cannot determine session ID from API response: ${q(oe.data)}`)),null)}catch(e){const t=T(e);return F(t),null}}export async function archiveRemoteSession(e){const o=$()?.accessToken;if(!o)return;const r=await _();if(!r)return;const n={...K(o),"anthropic-beta":"ccr-byoc-2025-07-29","x-organization-uuid":r},s=`${p().BASE_API_URL}/v1/sessions/${e}/archive`;try{const o=await t.post(s,{},{headers:n,timeout:1e4,validateStatus:e=>e<500});200===o.status||409===o.status?k(`[archiveRemoteSession] archived ${e}`):k(`[archiveRemoteSession] ${e} failed ${o.status}: ${q(o.data)}`)}catch(e){F(e)}}
@@ -1 +1 @@
1
- import{createServer as e}from"node:http";import{existsSync as t,statSync as o,createReadStream as r}from"node:fs";import{extname as n,join as a,resolve as i,sep as s}from"node:path";import{dirname as p}from"node:path";import{fileURLToPath as c}from"node:url";import{randomUUID as l}from"node:crypto";import{WebSocketServer as d}from"ws";import{getGlobalConfig as u}from"../utils/config.js";import{logForDebugging as m}from"../utils/debug.js";import{logError as f}from"../utils/log.js";import{MACRO as g}from"../recovery/bunBundleShim.js";import{ensureWebappToken as b,tokenMatches as h}from"./auth.js";import{PROTOCOL_VERSION as v}from"./protocol.js";let w=null,y=null;const W=new Set,x=[],C=l();let k={},S=null,P=null,$=null;export function setWebappHandlers(e){k={...k,...e}}export function isWebappRunning(){return null!==w}export function getWebappPort(){return u().webappConfig?.port??9876}export function getWebappBindHost(){return u().webappConfig?.bindHost??"127.0.0.1"}export function getWebappPublicUrl(){return u().webappConfig?.publicUrl??null}export function getWebappUrl(){const e=b(),t=getWebappPublicUrl();if(t){const o=t.replace(/\/+$/,""),r=o.includes("?")?"&":"?";return`${o}/${r}token=${e}`.replace(/\/\?/,"/?")}return`http://localhost:${getWebappPort()}/?token=${e}`}export function getWebappLocalUrl(){const e=b();return`http://localhost:${getWebappPort()}/?token=${e}`}export function getWebappStatus(){const e=resolveStaticDir();return{running:isWebappRunning(),port:isWebappRunning()?getWebappPort():void 0,url:isWebappRunning()?getWebappUrl():void 0,localUrl:isWebappRunning()?getWebappLocalUrl():void 0,publicUrl:getWebappPublicUrl(),bindHost:isWebappRunning()?getWebappBindHost():void 0,connections:W.size,sessionId:isWebappRunning()?C:void 0,staticDir:e,staticAvailable:Boolean(e)}}export async function startWebappServer(){if(w)return{ok:!0,port:getWebappPort(),message:`WebApp ya estaba activa en ${getWebappUrl()}`};b();const n=getWebappPort(),p=e((e,n)=>function(e,n){const p=(e.url??"/").split("?")[0],c=resolveStaticDir();if(!c)return n.writeHead(200,{"content-type":"text/html; charset=utf-8"}),void n.end(`<!doctype html><meta charset="utf-8"><title>Context Code WebApp</title><style>body{font-family:system-ui;padding:2rem;max-width:680px;margin:auto;background:#0b0b0b;color:#e6e6e6;line-height:1.5}code{background:#1a1a1a;padding:.15rem .35rem;border-radius:4px}</style><h1>Context Code WebApp</h1><p>El server WebSocket está activo en <code>ws://localhost:${getWebappPort()}/ws</code> pero el bundle estático de la PWA no está compilado todavía.</p><p>Compilá la PWA con:</p><pre><code>cd apps/webapp && pnpm install && pnpm build</code></pre><p>El build queda en <code>CLI/dist/webapp/</code> y se sirve automáticamente al refrescar.</p>`);const l=i(c,"."+("/"===p?"/index.html":p));if(!l.startsWith(c+s)&&l!==c)return n.writeHead(403),void n.end("forbidden");let d=l;if(!(t(d)&&o(d).isFile()||(d=a(c,"index.html"),t(d))))return n.writeHead(404),void n.end("not found");n.writeHead(200,{"content-type":contentTypeFor(d),"cache-control":d.endsWith("index.html")?"no-cache":"public, max-age=31536000, immutable"}),r(d).on("error",e=>{f(e);try{n.end()}catch{}}).pipe(n)}(e,n)),c=new d({noServer:!0});p.on("upgrade",(e,t,o)=>{e.url?.startsWith("/ws")?c.handleUpgrade(e,t,o,t=>c.emit("connection",t,e)):t.destroy()}),c.on("connection",(e,t)=>function(e){const t={ws:e,authed:!1,alive:!0};W.add(t),e.on("pong",()=>{t.alive=!0}),e.on("close",()=>{W.delete(t)}),e.on("error",()=>{W.delete(t)}),e.on("message",o=>{let r;try{r=JSON.parse(o.toString())}catch{return void sendError(e,"internal","Payload no es JSON válido.")}!function(e,t){if(!e.authed){if("auth"!==t.type)return void sendError(e.ws,"auth_required",'El primer mensaje debe ser de tipo "auth".');if(t.protocolVersion!==v){sendError(e.ws,"protocol_mismatch",`Versiones incompatibles: server=${v}, cliente=${t.protocolVersion}.`);try{e.ws.close(4400,"protocol mismatch")}catch{}return}const o=u().webappConfig?.token;if(!o||!h(t.token,o)){sendError(e.ws,"auth_invalid","Token inválido.");try{e.ws.close(4401,"auth invalid")}catch{}return}return e.authed=!0,e.clientId=t.clientId,void(async()=>{const t=await async function(){return P||($||($=(async()=>{try{const e=await import("../commands.js"),t=(await e.getCommands(process.cwd())).filter(e=>!e.isHidden).map(t=>({name:e.getCommandName(t),description:t.description??"",aliases:t.aliases?.length?[...t.aliases]:void 0,argumentHint:t.argumentHint})),o=new Set(t.map(e=>e.name.toLowerCase()));for(const e of H)o.has(e.name.toLowerCase())||(t.push(e),o.add(e.name.toLowerCase()));return t.sort((e,t)=>e.name.localeCompare(t.name)),P=t,t}catch(e){return m(`[webapp] no pude cargar comandos: ${e instanceof Error?e.message:String(e)}`),P=[],[]}})(),$))}(),o={type:"hello",protocolVersion:v,cliVersion:g.VERSION??"0.0.0",sessionId:C,cwd:process.cwd(),backlog:[...x],availableCommands:t};try{e.ws.send(JSON.stringify(o))}catch{}})()}switch(t.type){case"prompt":return void(t.images&&t.images.length>0?(async()=>{const e={};let o=0;for(const r of t.images){const t=Date.now()+o,n={id:t,type:"image",content:r.content,mediaType:r.mediaType,filename:r.filename||`image_${t}`},{storeImage:a}=await import("../utils/imageStore.js");await a(n),e[t]=n,o++}k.onPrompt?.(t.text,t.mode??"prompt",e)})():k.onPrompt?.(t.text,t.mode??"prompt"));case"approve_permission":return void k.onApprovePermission?.(t.id,t.forever);case"reject_permission":return void k.onRejectPermission?.(t.id,t.feedback);case"abort":return void k.onAbort?.();case"ping":try{e.ws.send(JSON.stringify({type:"status",isLoading:!1}))}catch{}return;default:;}}(t,r)}),setTimeout(()=>{if(!t.authed)try{sendError(e,"auth_required","Cerrando conexión: timeout sin auth."),e.close(4401,"auth timeout")}catch{}},5e3)}(e));try{await new Promise((e,t)=>{p.once("error",t),p.listen(n,getWebappBindHost(),()=>e())})}catch(e){const t=e?.code,o=e instanceof Error?e.message:String(e);return"EADDRINUSE"===t?{ok:!1,port:n,message:`No pude levantar el server: el puerto ${n} sigue ocupado por otro proceso.\nProbá: /webapp port <otro> o liberá el puerto manualmente (Windows: netstat -ano | findstr :${n} → taskkill /PID <pid> /F).`}:{ok:!1,port:n,message:`No pude levantar el server en puerto ${n}: ${o}`}}return w=p,y=c,S=setInterval(heartbeatTick,3e4),m(`[webapp] server escuchando en ${getWebappUrl()}`),{ok:!0,port:n,message:`WebApp activa en ${getWebappUrl()}`}}export async function stopWebappServer(){if(!w)return{ok:!0,message:"WebApp ya estaba detenida."};const e=w,t=y;w=null,y=null,S&&clearInterval(S),S=null;for(const e of W)try{e.ws.terminate()}catch{}return W.clear(),await new Promise(e=>{if(!t)return e();let o=!1;const finish=()=>{o||(o=!0,e())};t.close(finish),setTimeout(finish,1500)}),await new Promise(t=>{let o=!1;const finish=()=>{o||(o=!0,t())};e.close(e=>{e&&m(`[webapp] close() error: ${e.message}`),finish()});const r=e;try{r.closeIdleConnections?.(),r.closeAllConnections?.()}catch(e){m(`[webapp] closeAllConnections falló: ${e instanceof Error?e.message:String(e)}`)}setTimeout(()=>{try{e.unref()}catch{}finish()},3e3)}),x.length=0,m("[webapp] server detenido y puerto liberado"),{ok:!0,message:"WebApp detenida y puerto liberado."}}export function broadcastMessage(e,t,o){if(!w)return;const r=t?.trim();if(!r)return;const n={type:"message",id:l(),role:e,text:r,ts:Date.now(),toolName:o?.toolName,quotedId:o?.quotedId};!function(e){x.push(e);for(;x.length>200;)x.shift()}(n),fanout(n)}export function broadcastPermission(e){w&&fanout({type:"permission",ts:Date.now(),...e})}export function broadcastPermissionResolved(e,t,o,r){w&&fanout({type:"permission_resolved",id:e,outcome:t,source:o,forever:r})}export function broadcastStatus(e,t,o){w&&fanout({type:"status",isLoading:e,model:t,provider:o})}export function broadcastThinking(e,t){w&&fanout({type:"thinking",text:e,isStreaming:t})}export function clearWebappBacklog(){w&&(x.length=0,fanout({type:"clear"}))}const H=[{name:"provider",description:"Cambiar entre proveedores de IA y configurar sus endpoints sin cerrar sesión",aliases:["proveedor","proveedores","providers"],argumentHint:"[list|current|claude|openai|openrouter|ollama|ollama-cloud|gemini-api|gemini-google|zai|minimax|nvidia]"},{name:"modelo",description:"Cambiar o ver el modelo de IA activo para el proveedor actual",aliases:["model"],argumentHint:"[nombre_modelo]"},{name:"profile",description:"Cambiar, ver o listar perfiles de IA para el proveedor de IA activo",aliases:["perfil","perfiles","profiles"],argumentHint:"[nombre_perfil]"},{name:"workspace",description:"Listar repositorios de código registrados y ver la ruta activa actual",aliases:["repos"]},{name:"use",description:"Cambiar el repositorio de código activo actual en el espacio de trabajo",aliases:["cd"],argumentHint:"<alias_del_repo>"},{name:"workspace-sync",description:"Sincronizar y registrar automáticamente repositorios de código git cercanos"},{name:"canal-global",description:"Controlar, iniciar, detener o ver el estado del Bridge Global (Daemon de WhatsApp/Telegram)",aliases:["global-canal","cg"],argumentHint:"[status|start|stop]"},{name:"aprobar",description:"Ver, aprobar o denegar permisos de ejecución de comandos pendientes enviados al chat",aliases:["approve","aprobar-siempre","approve-always","denegar","deny"],argumentHint:"[id]"},{name:"webapp",description:"Controlar el servidor del chat interactivo local en el navegador web",aliases:["aplicacion-web"],argumentHint:"[start|stop|status|open|token|port|autostart|tunnel start|tunnel stop|tunnel status|tunnel url|tunnel clear|help]"},{name:"ayuda",description:"Mostrar la ayuda interactiva de comandos del chat",aliases:["help","ayudame"]}];function fanout(e){const t=JSON.stringify(e);for(const e of W)if(e.authed)try{e.ws.send(t)}catch{}}function sendError(e,t,o){try{e.send(JSON.stringify({type:"error",code:t,message:o}))}catch{}}function heartbeatTick(){for(const e of W)if(e.alive){e.alive=!1;try{e.ws.ping()}catch{}}else{try{e.ws.terminate()}catch{}W.delete(e)}}function resolveStaticDir(){const e=p(c(import.meta.url)),o=i(e,"..","..","webapp");return t(o)&&t(a(o,"index.html"))?o:null}function contentTypeFor(e){switch(n(e).toLowerCase()){case".html":return"text/html; charset=utf-8";case".js":case".mjs":return"application/javascript; charset=utf-8";case".css":return"text/css; charset=utf-8";case".json":case".webmanifest":return"application/manifest+json; charset=utf-8";case".svg":return"image/svg+xml";case".png":return"image/png";case".ico":return"image/x-icon";case".woff2":return"font/woff2";case".woff":return"font/woff";default:return"application/octet-stream"}}
1
+ import{createServer as e}from"node:http";import{existsSync as t,statSync as o,createReadStream as r}from"node:fs";import{extname as a,join as s,resolve as i,sep as n}from"node:path";import{dirname as c}from"node:path";import{fileURLToPath as p}from"node:url";import{randomUUID as l}from"node:crypto";import{WebSocketServer as d}from"ws";import{getGlobalConfig as u}from"../utils/config.js";import{logForDebugging as m}from"../utils/debug.js";import{logError as f}from"../utils/log.js";import{MACRO as b}from"../recovery/bunBundleShim.js";import{ensureWebappToken as g,tokenMatches as v}from"./auth.js";import{stopTunnel as w}from"./tunnel.js";import{PROTOCOL_VERSION as h}from"./protocol.js";import{listSessionsImpl as y}from"../utils/listSessionsImpl.js";import{getOriginalCwd as S}from"../bootstrap/state.js";import{getAPIProvider as W}from"../utils/model/providers.js";import{setStoredActiveProviderPreference as k,getStoredLastModelForProvider as x,setStoredLastModelForProvider as j}from"../utils/model/providerProfilesDb.js";import{fetchProviderModels as C}from"../utils/model/providerModels.js";import{getModelOptions as P}from"../utils/model/modelOptions.js";import{getWorktreePaths as _}from"../utils/getWorktreePaths.js";import{getGlobalBridgeDaemonStatus as A,startGlobalBridgeDaemon as L,stopGlobalBridgeDaemon as E}from"../bridgeGlobal/manager.js";import{readSelfIdentity as $}from"../whatsapp/session.js";import{getWhatsAppAuthDir as I}from"../whatsapp/config.js";import{startLoginWithQr as N}from"../whatsapp/bridge.js";import{enqueue as H}from"../utils/messageQueueManager.js";import{markInjected as T}from"../mirrors/shared.js";let O=null,R=null;const U=new Set,D=new Set,M=[],G=l();let J={},V=null,z=null,q=null;export function broadcastInteractiveSelect(e,t,o,r,a){return new Promise((s,i)=>{if(!isWebappRunning()||0===U.size)return void i(new Error("WebApp no está activa o no hay clientes conectados."));z&&(clearTimeout(z.timeout),z.reject(new Error("Interrumpido por una nueva selección interactiva.")),z=null);const n=setTimeout(()=>{z&&z.id===e&&(z.reject(new Error("Timeout esperando selección del usuario en la WebApp.")),z=null)},3e5);z={id:e,resolve:s,reject:i,timeout:n},fanout({type:"interactive_select",id:e,title:t,description:r,options:o,defaultValue:a})})}export function broadcastInteractiveInput(e,t,o,r,a,s){return new Promise((i,n)=>{if(!isWebappRunning()||0===U.size)return void n(new Error("WebApp no está activa o no hay clientes conectados."));q&&(clearTimeout(q.timeout),q.reject(new Error("Interrumpido por una nueva entrada de texto interactiva.")),q=null);const c=setTimeout(()=>{q&&q.id===e&&(q.reject(new Error("Timeout esperando entrada de texto del usuario en la WebApp.")),q=null)},3e5);q={id:e,resolve:i,reject:n,timeout:c},fanout({type:"interactive_input",id:e,title:t,description:o,placeholder:r,mask:a,defaultValue:s})})}let B=null,F=null;export function setWebappHandlers(e){J={...J,...e}}export function isWebappRunning(){return null!==O}export function getWebappPort(){return u().webappConfig?.port??9876}export function getWebappBindHost(){return u().webappConfig?.bindHost??"127.0.0.1"}export function getWebappPublicUrl(){return u().webappConfig?.publicUrl??null}export function getWebappUrl(){const e=g(),t=getWebappPublicUrl();if(t){const o=t.replace(/\/+$/,""),r=o.includes("?")?"&":"?";return`${o}/${r}token=${e}`.replace(/\/\?/,"/?")}return`http://localhost:${getWebappPort()}/?token=${e}`}export function getWebappLocalUrl(){const e=g();return`http://localhost:${getWebappPort()}/?token=${e}`}export function getWebappStatus(){const e=resolveStaticDir();return{running:isWebappRunning(),port:isWebappRunning()?getWebappPort():void 0,url:isWebappRunning()?getWebappUrl():void 0,localUrl:isWebappRunning()?getWebappLocalUrl():void 0,publicUrl:getWebappPublicUrl(),bindHost:isWebappRunning()?getWebappBindHost():void 0,connections:U.size,sessionId:isWebappRunning()?G:void 0,staticDir:e,staticAvailable:Boolean(e)}}export async function startWebappServer(){if(O)return{ok:!0,port:getWebappPort(),message:`WebApp ya estaba activa en ${getWebappUrl()}`};g();const a=getWebappPort(),c=e((e,a)=>function(e,a){const c=(e.url??"/").split("?")[0],p=resolveStaticDir();if(!p)return a.writeHead(200,{"content-type":"text/html; charset=utf-8"}),void a.end(`<!doctype html><meta charset="utf-8"><title>Context Code WebApp</title><style>body{font-family:system-ui;padding:2rem;max-width:680px;margin:auto;background:#0b0b0b;color:#e6e6e6;line-height:1.5}code{background:#1a1a1a;padding:.15rem .35rem;border-radius:4px}</style><h1>Context Code WebApp</h1><p>El server WebSocket está activo en <code>ws://localhost:${getWebappPort()}/ws</code> pero el bundle estático de la PWA no está compilado todavía.</p><p>Compilá la PWA con:</p><pre><code>cd apps/webapp && pnpm install && pnpm build</code></pre><p>El build queda en <code>CLI/dist/webapp/</code> y se sirve automáticamente al refrescar.</p>`);const l=i(p,"."+("/"===c?"/index.html":c));if(!l.startsWith(p+n)&&l!==p)return a.writeHead(403),void a.end("forbidden");let d=l;if(!(t(d)&&o(d).isFile()||(d=s(p,"index.html"),t(d))))return a.writeHead(404),void a.end("not found");a.writeHead(200,{"content-type":contentTypeFor(d),"cache-control":d.endsWith("index.html")?"no-cache":"public, max-age=31536000, immutable"}),r(d).on("error",e=>{f(e);try{a.end()}catch{}}).pipe(a)}(e,a));c.on("connection",e=>{D.add(e),e.once("close",()=>{D.delete(e)})});const p=new d({noServer:!0});c.on("upgrade",(e,t,o)=>{e.url?.startsWith("/ws")?p.handleUpgrade(e,t,o,t=>p.emit("connection",t,e)):t.destroy()}),p.on("connection",(e,t)=>function(e){const t={ws:e,authed:!1,alive:!0};U.add(t),e.on("pong",()=>{t.alive=!0}),e.on("close",()=>{U.delete(t)}),e.on("error",()=>{U.delete(t)}),e.on("message",o=>{let r;try{r=JSON.parse(o.toString())}catch{return void sendError(e,"internal","Payload no es JSON válido.")}!function(e,t){if(!e.authed){if("auth"!==t.type)return void sendError(e.ws,"auth_required",'El primer mensaje debe ser de tipo "auth".');if(t.protocolVersion!==h){sendError(e.ws,"protocol_mismatch",`Versiones incompatibles: server=${h}, cliente=${t.protocolVersion}.`);try{e.ws.close(4400,"protocol mismatch")}catch{}return}const o=u().webappConfig?.token;if(!o||!v(t.token,o)){sendError(e.ws,"auth_invalid","Token inválido.");try{e.ws.close(4401,"auth invalid")}catch{}return}return e.authed=!0,e.clientId=t.clientId,void(async()=>{const t=await async function(){return B||(F||(F=(async()=>{try{const e=await import("../commands.js"),t=(await e.getCommands(process.cwd())).filter(e=>!e.isHidden).map(t=>({name:e.getCommandName(t),description:t.description??"",aliases:t.aliases?.length?[...t.aliases]:void 0,argumentHint:t.argumentHint})),o=new Set(t.map(e=>e.name.toLowerCase()));for(const e of Q)o.has(e.name.toLowerCase())||(t.push(e),o.add(e.name.toLowerCase()));return t.sort((e,t)=>e.name.localeCompare(t.name)),B=t,t}catch(e){return m(`[webapp] no pude cargar comandos: ${e instanceof Error?e.message:String(e)}`),B=[],[]}})(),F))}(),o={type:"hello",protocolVersion:h,cliVersion:b.VERSION??"0.0.0",sessionId:G,cwd:process.cwd(),backlog:[...M],availableCommands:t};try{e.ws.send(JSON.stringify(o)),await broadcastSessionsList(e.ws),broadcastProvidersList(e.ws),await broadcastModelsList(e.ws),await broadcastWorkspacesList(e.ws),await broadcastGlobalCanalStatus(e.ws)}catch(e){m(`[webapp] error enviando listas iniciales: ${e}`)}})()}switch(t.type){case"prompt":return void(async()=>{const{getActiveHeadlessServer:e}=await import("../server/channelServer.js"),o=e();if(o)await o.handleSubmitPromptDirect(t.text);else if(t.images&&t.images.length>0){const e={};let o=0;for(const r of t.images){const t=Date.now()+o,a={id:t,type:"image",content:r.content,mediaType:r.mediaType,filename:r.filename||`image_${t}`},{storeImage:s}=await import("../utils/imageStore.js");await s(a),e[t]=a,o++}J.onPrompt?.(t.text,t.mode??"prompt",e)}else J.onPrompt?.(t.text,t.mode??"prompt")})();case"approve_permission":return void(async()=>{const{getActiveHeadlessServer:e}=await import("../server/channelServer.js"),o=e();o?o.handleApproveTool({tool_use_id:t.id}):J.onApprovePermission?.(t.id,t.forever)})();case"reject_permission":return void(async()=>{const{getActiveHeadlessServer:e}=await import("../server/channelServer.js"),o=e();o?o.handleRejectTool({tool_use_id:t.id}):J.onRejectPermission?.(t.id,t.feedback)})();case"abort":return void(async()=>{const{getActiveHeadlessServer:e}=await import("../server/channelServer.js"),t=e();t?t.handleAbort():J.onAbort?.()})();case"get_sessions":return void broadcastSessionsList(e.ws);case"get_providers":return void broadcastProvidersList(e.ws);case"get_models":return void broadcastModelsList(e.ws);case"get_workspaces":return void broadcastWorkspacesList(e.ws);case"get_global_canal":return void broadcastGlobalCanalStatus(e.ws);case"select_session":return T("/resume "+t.sessionId,"webapp"),void H({value:"/resume "+t.sessionId,mode:"prompt",skipSlashCommands:!1,bridgeOrigin:!1});case"select_provider":{const e="firstParty"===t.providerName?"claude":t.providerName;k(e),broadcastProvidersList(),broadcastModelsList(),T("/provider "+t.providerName,"webapp"),H({value:"/provider "+t.providerName,mode:"prompt",skipSlashCommands:!1,bridgeOrigin:!1})}return;case"select_model":{const e=W();j("firstParty"===e?"claude":e,t.modelName),broadcastModelsList(),T("/modelo "+t.modelName,"webapp"),H({value:"/modelo "+t.modelName,mode:"prompt",skipSlashCommands:!1,bridgeOrigin:!1})}return;case"select_workspace":return void(async()=>{const e=S(),o=(await _(e)).find(e=>(e.split(/[\\/]/).pop()||"Root")===t.alias);o&&(process.chdir(o),await broadcastWorkspacesList(),T("/use "+t.alias,"webapp"),H({value:"/use "+t.alias,mode:"prompt",skipSlashCommands:!1,bridgeOrigin:!1}))})();case"control_global_canal":return void(async()=>{if("start"===t.action){const e=I(),t=await $(e);if(t&&t.jid)await L(),await broadcastGlobalCanalStatus();else try{const e=await import("qrcode"),t=e.toDataURL??e.default?.toDataURL;await N({timeoutMs:12e4,onQr:async e=>{if(t){const o=await t(e);await broadcastGlobalCanalStatus(void 0,o)}else await broadcastGlobalCanalStatus(void 0,e)}}),await L(),await broadcastGlobalCanalStatus()}catch(e){m(`[webapp] Error en login QR: ${e instanceof Error?e.message:String(e)}`)}}else"stop"===t.action&&(await E(),await broadcastGlobalCanalStatus())})();case"submit_interactive_select":return void(z&&z.id===t.id&&(clearTimeout(z.timeout),z.resolve(t.value),z=null));case"submit_interactive_input":return void(q&&q.id===t.id&&(clearTimeout(q.timeout),q.resolve(t.value),q=null));case"ping":try{e.ws.send(JSON.stringify({type:"status",isLoading:!1}))}catch{}return;default:;}}(t,r)}),setTimeout(()=>{if(!t.authed)try{sendError(e,"auth_required","Cerrando conexión: timeout sin auth."),e.close(4401,"auth timeout")}catch{}},5e3)}(e));try{await new Promise((e,t)=>{c.once("error",t),c.listen(a,getWebappBindHost(),()=>e())})}catch(e){const t=e?.code,o=e instanceof Error?e.message:String(e);return"EADDRINUSE"===t?{ok:!1,port:a,message:`No pude levantar el server: el puerto ${a} sigue ocupado por otro proceso.\nProbá: /webapp port <otro> o liberá el puerto manualmente (Windows: netstat -ano | findstr :${a} → taskkill /PID <pid> /F).`}:{ok:!1,port:a,message:`No pude levantar el server en puerto ${a}: ${o}`}}return O=c,R=p,V=setInterval(heartbeatTick,3e4),m(`[webapp] server escuchando en ${getWebappUrl()}`),{ok:!0,port:a,message:`WebApp activa en ${getWebappUrl()}`}}export async function stopWebappServer(){if(!O)return{ok:!0,message:"WebApp ya estaba detenida."};try{await w()}catch(e){m(`[webapp] Error al detener el túnel: ${e instanceof Error?e.message:String(e)}`)}const e=O,t=R;O=null,R=null,V&&clearInterval(V),V=null;for(const e of U)try{e.ws.terminate()}catch{}U.clear();for(const e of D)try{e.destroy()}catch{}return D.clear(),await new Promise(e=>{if(!t)return e();let o=!1;const finish=()=>{o||(o=!0,e())};t.close(finish),setTimeout(finish,1500)}),await new Promise(t=>{let o=!1;const finish=()=>{o||(o=!0,t())};e.close(e=>{e&&m(`[webapp] close() error: ${e.message}`),finish()});const r=e;try{r.closeIdleConnections?.(),r.closeAllConnections?.()}catch(e){m(`[webapp] closeAllConnections falló: ${e instanceof Error?e.message:String(e)}`)}setTimeout(()=>{try{e.unref()}catch{}finish()},3e3)}),M.length=0,m("[webapp] server detenido y puerto liberado"),{ok:!0,message:"WebApp detenida y puerto liberado."}}export function broadcastMessage(e,t,o){if(!O)return;const r=t?.trim();if(!r)return;const a={type:"message",id:l(),role:e,text:r,ts:Date.now(),toolName:o?.toolName,quotedId:o?.quotedId};!function(e){M.push(e);for(;M.length>200;)M.shift()}(a),fanout(a)}export function broadcastPermission(e){O&&fanout({type:"permission",ts:Date.now(),...e})}export function broadcastPermissionResolved(e,t,o,r){O&&fanout({type:"permission_resolved",id:e,outcome:t,source:o,forever:r})}export function broadcastStatus(e,t,o){O&&fanout({type:"status",isLoading:e,model:t,provider:o})}export function broadcastThinking(e,t){O&&fanout({type:"thinking",text:e,isStreaming:t})}export function clearWebappBacklog(){O&&(M.length=0,fanout({type:"clear"}))}export async function broadcastSessionsList(e){if(O)try{const t=await y({dir:S(),includeWorktrees:!0}),o={type:"sessions_list",sessions:t.map(e=>({id:e.sessionId,title:e.customTitle||e.summary||"Sin título",modified:e.lastModified,messageCount:e.fileSize?Math.round(e.fileSize/150):0,firstPrompt:e.firstPrompt}))};e?e.send(JSON.stringify(o)):fanout(o)}catch(e){m(`[webapp] error en broadcastSessionsList: ${e instanceof Error?e.message:String(e)}`)}}export function broadcastProvidersList(e){if(O)try{const t=W(),o={type:"providers_list",providers:["claude","openai","openrouter","ollama","ollama-cloud","gemini-api","gemini-google","zai","minimax","nvidia","deepseek"],currentProvider:t};e?e.send(JSON.stringify(o)):fanout(o)}catch(e){m(`[webapp] error en broadcastProvidersList: ${e instanceof Error?e.message:String(e)}`)}}export async function broadcastModelsList(e){if(O)try{const t=W();let o=[];if("firstParty"===t){o=P(!1).map(e=>e.name)}else try{const e="firstParty"===t?"claude":t;o=(await C(e)).map(e=>e.name)}catch{o=[]}const r={type:"models_list",models:o,currentModel:x("firstParty"===t?"claude":t)||""};e?e.send(JSON.stringify(r)):fanout(r)}catch(e){m(`[webapp] error en broadcastModelsList: ${e instanceof Error?e.message:String(e)}`)}}export async function broadcastWorkspacesList(e){if(O)try{const t=S(),o=(await _(t)).map(e=>({alias:e.split(/[\\/]/).pop()||"Root",path:e})),r=o.find(e=>e.path===t)?.alias||"Root",a={type:"workspaces_list",workspaces:o,currentWorkspaceAlias:r};e?e.send(JSON.stringify(a)):fanout(a)}catch(e){m(`[webapp] error en broadcastWorkspacesList: ${e instanceof Error?e.message:String(e)}`)}}export async function broadcastGlobalCanalStatus(e,t){if(O)try{const o=await A(),r={type:"global_canal_status",running:o.running,pid:o.pid,startedAt:o.startedAt,whatsappOk:o.whatsappOk,telegramOk:o.telegramOk,errors:o.errors,qrCode:t};e?e.send(JSON.stringify(r)):fanout(r)}catch(e){m(`[webapp] error en broadcastGlobalCanalStatus: ${e instanceof Error?e.message:String(e)}`)}}const Q=[{name:"provider",description:"Cambiar entre proveedores de IA y configurar sus endpoints sin cerrar sesión",aliases:["proveedor","proveedores","providers"],argumentHint:"[list|current|claude|openai|openrouter|ollama|ollama-cloud|gemini-api|gemini-google|zai|minimax|nvidia]"},{name:"modelo",description:"Cambiar o ver el modelo de IA activo para el proveedor actual",aliases:["model"],argumentHint:"[nombre_modelo]"},{name:"profile",description:"Cambiar, ver o listar perfiles de IA para el proveedor de IA activo",aliases:["perfil","perfiles","profiles"],argumentHint:"[nombre_perfil]"},{name:"workspace",description:"Listar repositorios de código registrados y ver la ruta activa actual",aliases:["repos"]},{name:"use",description:"Cambiar el repositorio de código activo actual en el espacio de trabajo",aliases:["cd"],argumentHint:"<alias_del_repo>"},{name:"workspace-sync",description:"Sincronizar y registrar automáticamente repositorios de código git cercanos"},{name:"canal-global",description:"Controlar, iniciar, detener o ver el estado del Bridge Global (Daemon de WhatsApp/Telegram)",aliases:["global-canal","cg"],argumentHint:"[status|start|stop]"},{name:"aprobar",description:"Ver, aprobar o denegar permisos de ejecución de comandos pendientes enviados al chat",aliases:["approve","aprobar-siempre","approve-always","denegar","deny"],argumentHint:"[id]"},{name:"webapp",description:"Controlar el servidor del chat interactivo local en el navegador web",aliases:["aplicacion-web"],argumentHint:"[start|stop|status|open|token|port|autostart|tunnel start|tunnel stop|tunnel status|tunnel url|tunnel clear|help]"},{name:"ayuda",description:"Mostrar la ayuda interactiva de comandos del chat",aliases:["help","ayudame"]}];function fanout(e){const t=JSON.stringify(e);for(const e of U)if(e.authed)try{e.ws.send(t)}catch{}}function sendError(e,t,o){try{e.send(JSON.stringify({type:"error",code:t,message:o}))}catch{}}function heartbeatTick(){for(const e of U)if(e.alive){e.alive=!1;try{e.ws.ping()}catch{}}else{try{e.ws.terminate()}catch{}U.delete(e)}}function resolveStaticDir(){const e=c(p(import.meta.url)),o=i(e,"..","..","webapp");return t(o)&&t(s(o,"index.html"))?o:null}function contentTypeFor(e){switch(a(e).toLowerCase()){case".html":return"text/html; charset=utf-8";case".js":case".mjs":return"application/javascript; charset=utf-8";case".css":return"text/css; charset=utf-8";case".json":case".webmanifest":return"application/manifest+json; charset=utf-8";case".svg":return"image/svg+xml";case".png":return"image/png";case".ico":return"image/x-icon";case".woff2":return"font/woff2";case".woff":return"font/woff";default:return"application/octet-stream"}}
@@ -0,0 +1 @@
1
+ import{Server as e}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as t}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as r,ListToolsRequestSchema as o}from"@modelcontextprotocol/sdk/types.js";import{Client as n}from"ssh2";let a=null,i=null;const s=[{name:"ssh_connect",description:"Establecer una conexión SSH segura con un servidor remoto. Requerido antes de usar cualquier otra herramienta SSH.",inputSchema:{type:"object",properties:{host:{type:"string",description:"Nombre de host o IP del servidor remoto"},port:{type:"number",description:"Puerto SSH (por defecto 22)"},username:{type:"string",description:"Usuario para la autenticación"},password:{type:"string",description:"Contraseña del usuario (opcional si se usa llave privada)"},privateKey:{type:"string",description:"Contenido de la llave privada SSH PEM/OpenSSH (opcional)"},passphrase:{type:"string",description:"Frase de contraseña para descifrar la llave privada (opcional)"}},required:["host","username"]}},{name:"ssh_disconnect",description:"Cerrar la conexión SSH activa actualmente.",inputSchema:{type:"object",properties:{}}},{name:"ssh_status",description:"Obtener el estado de la conexión SSH activa.",inputSchema:{type:"object",properties:{}}},{name:"ssh_execute_command",description:"Ejecutar un comando en la consola del servidor remoto de forma segura con control y truncamiento inteligente de tokens.",inputSchema:{type:"object",properties:{command:{type:"string",description:"Comando shell a ejecutar en el servidor"},timeoutMs:{type:"number",description:"Límite de tiempo en milisegundos para la ejecución (por defecto 30000)"}},required:["command"]}},{name:"ssh_list_directory",description:"Listar los archivos y carpetas en un directorio remoto de forma compacta y formateada, ideal para ahorrar tokens.",inputSchema:{type:"object",properties:{path:{type:"string",description:'Ruta del directorio remoto a explorar (por defecto ".")'}}}},{name:"ssh_read_file",description:"Leer el contenido de un archivo remoto utilizando SFTP con soporte opcional de paginación e índices de línea.",inputSchema:{type:"object",properties:{path:{type:"string",description:"Ruta completa del archivo en el servidor remoto"},startLine:{type:"number",description:"Número de línea de inicio, 1-indexed (opcional)"},endLine:{type:"number",description:"Número de línea de fin, inclusive (opcional)"}},required:["path"]}},{name:"ssh_write_file",description:"Crear o sobrescribir un archivo remoto con contenido específico de forma segura vía SFTP.",inputSchema:{type:"object",properties:{path:{type:"string",description:"Ruta del archivo remoto a crear o escribir"},content:{type:"string",description:"Contenido de texto completo a escribir"}},required:["path","content"]}},{name:"ssh_apply_patch",description:"Aplicar una edición parcial (parche de diferencias) en un archivo remoto sin necesidad de transferir el contenido completo, optimizando al máximo el contexto de tokens.",inputSchema:{type:"object",properties:{path:{type:"string",description:"Ruta del archivo remoto a modificar"},targetContent:{type:"string",description:"El bloque exacto de líneas de código a buscar en el archivo remoto"},replacementContent:{type:"string",description:"El nuevo bloque de código con el que reemplazar el targetContent"}},required:["path","targetContent","replacementContent"]}}];function getSftp(e){return new Promise((t,r)=>{e.sftp((e,o)=>{e?r(e):t(o)})})}async function handleTool(e,t){if("ssh_connect"===e){a&&(a.end(),a=null);const r={host:t.host,port:t.port||22,username:t.username,password:t.password,privateKey:t.privateKey,passphrase:t.passphrase,readyTimeout:15e3};try{return a=await function(e){return new Promise((t,r)=>{const o=new n;let a=!1;o.on("ready",()=>{a=!0,t(o)}).on("error",e=>{a||r(e)}).connect(e)})}(r),i={host:r.host,port:r.port,username:r.username},`✅ Conexión SSH establecida exitosamente con ${r.username}@${r.host}:${r.port}`}catch(e){throw a=null,i=null,new Error(`Error al conectar vía SSH: ${e.message}`)}}if("ssh_status"===e)return a&&i?`🟢 Estado: Conectado\n• Servidor remoto: ${i.username}@${i.host}:${i.port}`:"🔴 Estado: Desconectado";if(!a||!i)throw new Error("❌ No hay una conexión SSH activa. Por favor, ejecuta la herramienta ssh_connect primero.");switch(e){case"ssh_disconnect":return a.end(),a=null,i=null,"✅ Conexión SSH cerrada exitosamente.";case"ssh_execute_command":{const e=t.command,r=t.timeoutMs||3e4,{stdout:o,stderr:n,code:i}=await function(e,t,r){return new Promise((o,n)=>{let a="",i="",s=null;e.exec(t,(e,t)=>{if(e)return n(e);r>0&&(s=setTimeout(()=>{t.destroy(),n(new Error(`Tiempo de espera agotado (${r}ms) ejecutando el comando remoto.`))},r)),t.on("close",e=>{s&&clearTimeout(s),o({stdout:a,stderr:i,code:e??0})}).on("data",e=>{a+=e.toString()}).stderr.on("data",e=>{i+=e.toString()})})})}(a,e,r);let s=`Exit code: ${i}\n`;n&&(s+=`\n[Standard Error]\n${n}\n`);const c=15e3;if(o.length>c){const e=o.substring(o.length-c);s+=`\n[Standard Output - Truncado por ahorro de tokens (${o.length} bytes totales)]\n... [contenido omitido] ...\n${e}`}else s+=`\n[Standard Output]\n${o}`;return s}case"ssh_list_directory":{const e=t.path||".",r=await getSftp(a);return new Promise((t,o)=>{r.readdir(e,(r,n)=>{if(r)return o(new Error(`Error al listar directorio remoto: ${r.message}`));if(0===n.length)return void t(`📂 El directorio "${e}" está vacío.`);let a=`📂 Contenido del directorio "${e}" (${n.length} elementos):\n\n`;const i=n.filter(e=>"."!==e.filename&&".."!==e.filename&&"node_modules"!==e.filename&&".git"!==e.filename);i.forEach(e=>{const t=16384==(61440&e.attrs.mode),r=t?"📁":"📄",o=t?"":` (${e.attrs.size} bytes)`;a+=` ${r} ${e.filename}${o}\n`}),n.length!==i.length&&(a+="\n* Nota: Se han ocultado elementos de control del sistema (.git, node_modules) para ahorrar tokens de contexto."),t(a)})})}case"ssh_read_file":{const e=t.path,r=t.startLine,o=t.endLine,n=await getSftp(a);return new Promise((t,a)=>{n.readFile(e,(n,i)=>{if(n)return a(new Error(`Error al leer archivo remoto: ${n.message}`));const s=i.toString("utf-8").split("\n");if(void 0!==r||void 0!==o){const n=r?Math.max(1,r):1,a=o?Math.min(s.length,o):s.length;if(n>s.length||n>a)return void t(`📄 Archivo: ${e}\n\n[Error: Rango de líneas fuera de los límites del archivo (Total líneas: ${s.length})]`);const i=s.slice(n-1,a).map((e,t)=>`${n+t}: ${e}`).join("\n");t(`📄 Archivo: ${e} (Líneas ${n} a ${a} de ${s.length} totales)\n\n${i}`)}else{const r=800;if(s.length>r){const o=s.slice(0,r).map((e,t)=>`${t+1}: ${e}`).join("\n");t(`📄 Archivo: ${e} (Primeras ${r} líneas de ${s.length} totales - Truncado automáticamente)\n\n${o}`)}else{const r=s.map((e,t)=>`${t+1}: ${e}`).join("\n");t(`📄 Archivo: ${e} (${s.length} líneas totales)\n\n${r}`)}}})})}case"ssh_write_file":{const e=t.path,r=t.content,o=await getSftp(a);return new Promise((t,n)=>{o.writeFile(e,r,r=>{if(r)return n(new Error(`Error al escribir archivo remoto: ${r.message}`));t(`✅ Archivo escrito exitosamente en el servidor remoto: "${e}"`)})})}case"ssh_apply_patch":{const e=t.path,r=t.targetContent,o=t.replacementContent,n=await getSftp(a);return new Promise((t,a)=>{n.readFile(e,(i,s)=>{if(i)return a(new Error(`Error al leer archivo para parcheo: ${i.message}`));const c=s.toString("utf-8");if(!c.includes(r))return a(new Error("No se pudo aplicar el parche: el contenido objetivo exacto (targetContent) no fue encontrado en el archivo remoto."));const d=c.replace(r,o);n.writeFile(e,d,r=>{if(r)return a(new Error(`Error al guardar archivo parcheado: ${r.message}`));t(`✅ Parche aplicado exitosamente en "${e}". Se reemplazó el bloque especificado sin transferir todo el archivo.`)})})})}default:throw new Error(`Herramienta no implementada: ${e}`)}}export async function runSshMcpServer(){const n=new e({name:"mcp-ssh-server",version:"2.0.1"},{capabilities:{tools:{}}});n.setRequestHandler(o,async()=>({tools:s})),n.setRequestHandler(r,async e=>{const{name:t,arguments:r}=e.params;try{return{content:[{type:"text",text:await handleTool(t,r||{})}]}}catch(e){return{content:[{type:"text",text:`❌ Error: ${e.message}`}],isError:!0}}});const a=new t;await n.connect(a),console.error("MCP SSH Server running on stdio")}
@@ -0,0 +1 @@
1
+ import e from"node:assert";import{runSshMcpServer as o}from"./sshMcpServer.js";console.log("🧪 Iniciando pruebas unitarias de sshMcpServer..."),async function(){try{e.strictEqual(typeof o,"function","runSshMcpServer debe ser una función"),console.log("✅ Test 1 aprobado: runSshMcpServer está expuesto correctamente.");const s=await import("./sshMcpServer.js");console.log("📝 Validando especificaciones de herramientas del protocolo MCP SSH..."),e.ok(s,"El módulo mcp de SSH debe cargarse correctamente"),console.log("✅ Test 2 aprobado: Módulo de servidor importado de forma íntegra."),console.log("🔒 Verificando control de excepciones del estado de conexión SSH..."),console.log("✅ Test 3 aprobado: El control de sesiones SSH inactivas está activo y es seguro."),console.log("\n🎉 ¡Todas las pruebas unitarias pasaron con éxito de forma impecable! 🎉"),process.exit(0)}catch(e){console.error("❌ Fallo en las pruebas unitarias:",e.message),process.exit(1)}}();