@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.
- package/dist/src/bridge/bridgeMain.js +1 -1
- package/dist/src/bridge/types.js +1 -1
- package/dist/src/cli/handlers/auth.js +1 -1
- package/dist/src/cli/handlers/mcp.js +1 -1
- package/dist/src/cli/print.js +1 -1
- package/dist/src/commands/copy/copy.js +1 -1
- package/dist/src/commands/heartbeat/heartbeat.js +1 -0
- package/dist/src/commands/heartbeat/index.js +1 -0
- package/dist/src/commands/insights.js +1 -1
- package/dist/src/commands/install-github-app/ExistingWorkflowStep.js +1 -1
- package/dist/src/commands/install-github-app/InstallAppStep.js +1 -1
- package/dist/src/commands/install-github-app/OAuthFlowStep.js +1 -1
- package/dist/src/commands/install-github-app/SuccessStep.js +1 -1
- package/dist/src/commands/install-github-app/setupGitHubActions.js +1 -1
- package/dist/src/commands/login-openai/index.js +1 -1
- package/dist/src/commands/mobile/mobile.js +1 -1
- package/dist/src/commands/remote-setup/remote-setup.js +1 -1
- package/dist/src/commands/swarm-auto/swarmAuto.js +1 -1
- package/dist/src/commands/timeline/timeline.js +1 -1
- package/dist/src/commands/webapp/webapp.js +1 -1
- package/dist/src/commands.js +1 -1
- package/dist/src/components/ApproveApiKey.js +1 -1
- package/dist/src/components/ChannelDowngradeDialog.js +1 -1
- package/dist/src/components/ClaudeCodeHint/PluginHintMenu.js +1 -1
- package/dist/src/components/ConsoleOAuthFlow.js +1 -1
- package/dist/src/components/CostThresholdDialog.js +1 -1
- package/dist/src/components/CtrlOToExpand.js +1 -1
- package/dist/src/components/CustomSelect/select.js +1 -1
- package/dist/src/components/DesktopHandoff.js +1 -1
- package/dist/src/components/DesktopUpsell/DesktopUpsellStartup.js +1 -1
- package/dist/src/components/ExportDialog.js +1 -1
- package/dist/src/components/Feedback.js +1 -1
- package/dist/src/components/FeedbackSurvey/FeedbackSurvey.js +1 -1
- package/dist/src/components/FeedbackSurvey/FeedbackSurveyView.js +1 -1
- package/dist/src/components/IdeAutoConnectDialog.js +1 -1
- package/dist/src/components/IdeOnboardingDialog.js +1 -1
- package/dist/src/components/IdleReturnDialog.js +1 -1
- package/dist/src/components/InterruptedByUser.js +1 -1
- package/dist/src/components/InvalidConfigDialog.js +1 -1
- package/dist/src/components/InvalidSettingsDialog.js +1 -1
- package/dist/src/components/LanguagePicker.js +1 -1
- package/dist/src/components/LogSelector.js +1 -1
- package/dist/src/components/LogoV2/ChannelsNotice.js +1 -1
- package/dist/src/components/LogoV2/LogoV2.js +1 -1
- package/dist/src/components/LogoV2/feedConfigs.js +1 -1
- package/dist/src/components/LspRecommendation/LspRecommendationMenu.js +1 -1
- package/dist/src/components/MCPServerApprovalDialog.js +1 -1
- package/dist/src/components/MCPServerDesktopImportDialog.js +1 -1
- package/dist/src/components/ManagedSettingsSecurityDialog/ManagedSettingsSecurityDialog.js +1 -1
- package/dist/src/components/MessageSelector.js +1 -1
- package/dist/src/components/OutputStylePicker.js +1 -1
- package/dist/src/components/PromptInput/PromptInput.js +1 -1
- package/dist/src/components/RemoteCallout.js +1 -1
- package/dist/src/components/ResumeTask.js +1 -1
- package/dist/src/components/Settings/Config.js +1 -1
- package/dist/src/components/ShowInIDEPrompt.js +1 -1
- package/dist/src/components/Stats.js +1 -1
- package/dist/src/components/TagTabs.js +1 -1
- package/dist/src/components/TeleportError.js +1 -1
- package/dist/src/components/TeleportResumeWrapper.js +1 -1
- package/dist/src/components/TextInput.js +1 -1
- package/dist/src/components/agents/new-agent-creation/wizard-steps/MethodStep.js +1 -1
- package/dist/src/components/design-system/KeyboardShortcutHint.js +1 -1
- package/dist/src/components/diff/DiffDetailView.js +1 -1
- package/dist/src/components/grove/Grove.js +1 -1
- package/dist/src/components/hooks/SelectEventMode.js +1 -1
- package/dist/src/components/hooks/SelectHookMode.js +1 -1
- package/dist/src/components/hooks/SelectMatcherMode.js +1 -1
- package/dist/src/components/hooks/ViewHookMode.js +1 -1
- package/dist/src/components/mcp/MCPAgentServerMenu.js +1 -1
- package/dist/src/components/mcp/utils/reconnectHelpers.js +1 -1
- package/dist/src/components/memory/MemoryFileSelector.js +1 -1
- package/dist/src/components/messages/AssistantTextMessage.js +1 -1
- package/dist/src/components/messages/PlanApprovalMessage.js +1 -1
- package/dist/src/components/messages/ShutdownMessage.js +1 -1
- package/dist/src/components/messages/SystemAPIErrorMessage.js +1 -1
- package/dist/src/components/messages/UserPlanMessage.js +1 -1
- package/dist/src/components/permissions/AskUserQuestionPermissionRequest/PreviewQuestionView.js +1 -1
- package/dist/src/components/permissions/ComputerUseApproval/ComputerUseApproval.js +1 -1
- package/dist/src/components/permissions/FileEditPermissionRequest/FileEditPermissionRequest.js +1 -1
- package/dist/src/components/permissions/FilePermissionDialog/FilePermissionDialog.js +1 -1
- package/dist/src/components/permissions/FilePermissionDialog/permissionOptions.js +1 -1
- package/dist/src/components/permissions/FileWritePermissionRequest/FileWritePermissionRequest.js +1 -1
- package/dist/src/components/permissions/FilesystemPermissionRequest/FilesystemPermissionRequest.js +1 -1
- package/dist/src/components/permissions/PermissionRequest.js +1 -1
- package/dist/src/components/permissions/SedEditPermissionRequest/SedEditPermissionRequest.js +1 -1
- package/dist/src/components/permissions/SkillPermissionRequest/SkillPermissionRequest.js +1 -1
- package/dist/src/components/permissions/WebFetchPermissionRequest/WebFetchPermissionRequest.js +1 -1
- package/dist/src/components/permissions/rules/AddPermissionRules.js +1 -1
- package/dist/src/components/permissions/rules/AddWorkspaceDirectory.js +1 -1
- package/dist/src/components/permissions/rules/PermissionRuleList.js +1 -1
- package/dist/src/components/permissions/rules/RemoveWorkspaceDirectory.js +1 -1
- package/dist/src/components/sandbox/SandboxConfigTab.js +1 -1
- package/dist/src/components/sandbox/SandboxOverridesTab.js +1 -1
- package/dist/src/components/tasks/RemoteSessionDetailDialog.js +1 -1
- package/dist/src/constants/outputStyles.js +1 -1
- package/dist/src/entrypoints/cli.js +1 -1
- package/dist/src/hooks/notifs/useCanSwitchToExistingSubscription.js +1 -1
- package/dist/src/main.js +1 -1
- package/dist/src/projectOnboardingState.js +1 -1
- package/dist/src/screens/REPL.js +1 -1
- package/dist/src/server/channelServer.js +1 -0
- package/dist/src/server/channelServer.test.js +1 -0
- package/dist/src/services/api/errors.js +1 -1
- package/dist/src/services/api/filesApi.js +1 -1
- package/dist/src/services/api/index.js +1 -1
- package/dist/src/services/tips/tipRegistry.js +1 -1
- package/dist/src/tasks/RemoteAgentTask/RemoteAgentTask.js +1 -1
- package/dist/src/tools/AgentTool/AgentTool.js +1 -1
- package/dist/src/tools/ConfigTool/supportedSettings.js +1 -1
- package/dist/src/tools/EnterWorktreeTool/EnterWorktreeTool.js +1 -1
- package/dist/src/tools/ExitWorktreeTool/ExitWorktreeTool.js +1 -1
- package/dist/src/tools/WebFetchTool/WebFetchTool.js +1 -1
- package/dist/src/utils/computerUse/cleanup.js +1 -1
- package/dist/src/utils/computerUse/wrapper.js +1 -1
- package/dist/src/utils/fileHistory.js +1 -1
- package/dist/src/utils/heartbeat.js +1 -0
- package/dist/src/utils/model/providers.js +1 -1
- package/dist/src/utils/permissions/filesystem.js +1 -1
- package/dist/src/utils/permissions/permissions.js +1 -1
- package/dist/src/utils/processUserInput/processSlashCommand.js +1 -1
- package/dist/src/utils/releaseNoteTranslations.js +1 -1
- package/dist/src/utils/shell/shellToolUtils.js +1 -1
- package/dist/src/utils/sideQuery.js +1 -1
- package/dist/src/utils/sshMcp/common.js +1 -0
- package/dist/src/utils/sshMcp/setup.js +1 -0
- package/dist/src/utils/sshMcp/sshMcpServer.js +1 -0
- package/dist/src/utils/sshMcp/sshMcpServer.test.js +1 -0
- package/dist/src/utils/teleport.js +1 -1
- package/dist/src/webapp/server.js +1 -1
- package/dist/sshMcpServer.js +1 -0
- package/dist/sshMcpServer.test.js +1 -0
- package/dist/webapp/chunk-AMCDNAIG.js +1 -0
- package/dist/webapp/{chunk-YUEYJPXQ.js → chunk-NFYBHCXF.js} +1 -1
- package/dist/webapp/{chunk-DFKSSBHI.js → chunk-OJZNEHPP.js} +1 -1
- package/dist/webapp/chunk-VAB2VXFI.js +1 -0
- package/dist/webapp/index.html +2 -2
- package/dist/webapp/main-MTQLKGXD.js +1 -0
- package/dist/webapp/ngsw.json +14 -14
- package/dist/webapp/styles-DIKEDJBH.css +1 -0
- package/package.json +2 -1
- package/dist/webapp/chunk-4SRNXNLW.js +0 -1
- package/dist/webapp/chunk-SIHYW6PA.js +0 -1
- package/dist/webapp/main-LBNRQBXX.js +0 -1
- package/dist/webapp/styles-FUPULZDX.css +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{MACRO as e,feature as s}from"../recovery/bunBundleShim.js";import{randomUUID as t}from"crypto";import{hostname as o,tmpdir as r}from"os";import{basename as i,join as n,resolve as a}from"path";import{getRemoteSessionUrl as l}from"../constants/product.js";import{shutdownDatadog as d}from"../services/analytics/datadog.js";import{shutdown1PEventLogging as c}from"../services/analytics/firstPartyEventLogger.js";import{checkGate_CACHED_OR_BLOCKING as p}from"../services/analytics/growthbook.js";import{logEvent as u,logEventAsync as g}from"../services/analytics/index.js";import{isInBundledMode as m}from"../utils/bundledMode.js";import{logForDebugging as w}from"../utils/debug.js";import{logForDiagnosticsNoPII as f}from"../utils/diagLogs.js";import{isEnvTruthy as h,isInProtectedNamespace as _}from"../utils/envUtils.js";import{errorMessage as b}from"../utils/errors.js";import{truncateToWidth as k}from"../utils/format.js";import{logError as v}from"../utils/log.js";import{sleep as S}from"../utils/sleep.js";import{createAgentWorktree as $,removeAgentWorktree as y}from"../utils/worktree.js";import{BridgeFatalError as I,createBridgeApiClient as E,isExpiredErrorType as M,isSuppressible403 as R,validateBridgeId as C}from"./bridgeApi.js";import{formatDuration as x}from"./bridgeStatusUtil.js";import{createBridgeLogger as T}from"./bridgeUI.js";import{createCapacityWake as D}from"./capacityWake.js";import{describeAxiosError as A}from"./debugUtils.js";import{createTokenRefreshScheduler as U}from"./jwtUtils.js";import{getPollIntervalConfig as P}from"./pollConfig.js";import{toCompatSessionId as j,toInfraSessionId as B}from"./sessionIdCompat.js";import{createSessionSpawner as W,safeFilenameId as F}from"./sessionRunner.js";import{getTrustedDeviceToken as O}from"./trustedDevice.js";import{BRIDGE_LOGIN_ERROR as N,DEFAULT_SESSION_TIMEOUT_MS as G}from"./types.js";import{buildCCRv2SdkUrl as V,buildSdkUrl as H,decodeWorkSecret as L,registerWorker as z,sameSessionId as q}from"./workSecret.js";const K={connInitialMs:2e3,connCapMs:12e4,connGiveUpMs:6e5,generalInitialMs:500,generalCapMs:3e4,generalGiveUpMs:6e5};async function isMultiSessionSpawnEnabled(){return p("tengu_ccr_bridge_multi_session")}function pollSleepDetectionThresholdMs(e){return 2*e.connCapMs}function spawnScriptArgs(){return m()||!process.argv[1]?[]:[process.argv[1]]}function safeSpawn(e,s,t){try{return e.spawn(s,t)}catch(e){const s=b(e);return v(new Error(`Session spawn failed: ${s}`)),s}}export async function runBridgeLoop(e,t,o,i,a,d,c,p=K,g,m){const k=new AbortController;c.aborted?k.abort():c.addEventListener("abort",()=>k.abort(),{once:!0});const E=k.signal,T=new Map,B=new Map,W=new Map,O=new Map,N=new Map,Y=new Map,J=new Set,X=new Map,Q=new Set,Z=new Set,ee=D(E);async function heartbeatActiveWorkItems(){let e=!1,s=!1;const o=[];for(const[r]of T){const n=W.get(r),a=N.get(r);if(n&&a)try{await i.heartbeatWork(t,n,a),e=!0}catch(e){w(`[bridge:heartbeat] Failed for sessionId=${r} workId=${n}: ${b(e)}`),e instanceof I&&(u("tengu_bridge_heartbeat_error",{status:e.status,error_type:401===e.status||403===e.status?"auth_failed":"fatal"}),401===e.status||403===e.status?o.push(r):s=!0)}}for(const e of o){d.logVerbose(`Session ${e} token expired — re-queuing via bridge/reconnect`);try{await i.reconnectSession(t,e),w(`[bridge:heartbeat] Re-queued sessionId=${e} via bridge/reconnect`)}catch(s){d.logError(`Failed to refresh session ${e} token: ${b(s)}`),w(`[bridge:heartbeat] reconnectSession(${e}) failed: ${b(s)}`,{level:"error"})}}return s?"fatal":o.length>0?"auth_failed":e?"ok":"failed"}const se=new Set,te=m?U({getAccessToken:m,onRefresh:(e,s)=>{const o=T.get(e);o&&(se.has(e)?(d.logVerbose(`Refreshing session ${e} token via bridge/reconnect`),i.reconnectSession(t,e).catch(s=>{d.logError(`Failed to refresh session ${e} token: ${b(s)}`),w(`[bridge:token] reconnectSession(${e}) failed: ${b(s)}`,{level:"error"})})):o.updateAccessToken(s))},label:"bridge"}):null,oe=Date.now(),re=new Set;function trackCleanup(e){re.add(e),e.finally(()=>re.delete(e))}let ie=0,ne=0,ae=null,le=null,de=null,ce=null,pe=!1;if(w(`[bridge:work] Starting poll loop spawnMode=${e.spawnMode} maxSessions=${e.maxSessions} environmentId=${t}`),f("info","bridge_loop_started",{max_sessions:e.maxSessions,spawn_mode:e.spawnMode}),"ant"===process.env.USER_TYPE){let s;if(e.debugFile){const t=e.debugFile.lastIndexOf(".");s=t>0?`${e.debugFile.slice(0,t)}-*${e.debugFile.slice(t)}`:`${e.debugFile}-*`}else s=n(r(),"claude","bridge-session-*.log");d.setDebugLogPath(s)}function updateStatusDisplay(){d.updateSessionCount(T.size,e.maxSessions,e.spawnMode);for(const[e,s]of T){const t=s.currentActivity;t&&d.updateSessionActivity(O.get(e)??e,t)}if(0===T.size)return void d.updateIdleStatus();const[s,t]=[...T.entries()].pop(),o=B.get(s);if(!o)return;const r=t.currentActivity;if(!r||"result"===r.type||"error"===r.type)return void(e.maxSessions>1&&d.refreshDisplay());const i=x(Date.now()-o),n=t.activities.filter(e=>"tool_start"===e.type).slice(-5).map(e=>e.summary);d.updateSessionStatus(s,i,r,n)}function startStatusUpdates(){stopStatusUpdates(),updateStatusDisplay(),ce=setInterval(updateStatusDisplay,1e3)}function stopStatusUpdates(){ce&&(clearInterval(ce),ce=null)}function onSessionDone(s,o,r){return n=>{const a=W.get(s);T.delete(s),B.delete(s),W.delete(s),N.delete(s);const l=O.get(s)??s;O.delete(s),d.removeSession(l),Z.delete(l),se.delete(s);const c=Y.get(s);c&&(clearTimeout(c),Y.delete(s)),te?.cancel(s),ee.wake();const g=Q.delete(s),m=g&&"interrupted"===n?"failed":n,h=Date.now()-o;w(`[bridge:session] sessionId=${s} workId=${a??"unknown"} exited status=${m} duration=${x(h)}`),u("tengu_bridge_session_done",{status:m,duration_ms:h}),f("info","bridge_session_done",{status:m,duration_ms:h}),d.clearStatus(),stopStatusUpdates();const _=r.lastStderr.length>0?r.lastStderr.join("\n"):void 0;let S;switch(m){case"completed":d.logSessionComplete(s,h);break;case"failed":g||E.aborted||(S=_??"Process exited with error",d.logSessionFailed(s,S),v(new Error(`Bridge session failed: ${S}`)));break;case"interrupted":d.logVerbose(`Session ${s} interrupted`)}"interrupted"!==m&&a&&(trackCleanup(stopWorkWithRetry(i,t,a,d,p.stopWorkBaseDelayMs)),J.add(a));const $=X.get(s);if($&&(X.delete(s),trackCleanup(y($.worktreePath,$.worktreeBranch,$.gitRoot,$.hookBased).catch(e=>d.logVerbose(`Failed to remove worktree ${$.worktreePath}: ${b(e)}`)))),"interrupted"!==m&&!E.aborted){if("single-session"===e.spawnMode)return w(`[bridge:session] Session ${m}, aborting poll loop to tear down environment`),void k.abort();trackCleanup(i.archiveSession(l).catch(e=>d.logVerbose(`Failed to archive session ${s}: ${b(e)}`))),w(`[bridge:session] Session ${m}, returning to idle (multi-session mode)`)}E.aborted||startStatusUpdates()}}for(d.printBanner(e,t),d.updateSessionCount(0,e.maxSessions,e.spawnMode),g&&d.setAttached(g),g||startStatusUpdates();!E.aborted;){const s=P();try{const c=await i.pollForWork(t,o,E,s.reclaim_older_than_ms);if(null!==ae||null!==le){const e=Date.now()-(ae??le??Date.now());d.logReconnected(e),w(`[bridge:poll] Reconnected after ${x(e)}`),u("tengu_bridge_reconnected",{disconnected_ms:e})}if(ie=0,ne=0,ae=null,le=null,de=null,!c){if(T.size>=e.maxSessions){const t=s.multisession_poll_interval_ms_at_capacity;if(s.non_exclusive_heartbeat_interval_ms>0){u("tengu_bridge_heartbeat_mode_entered",{active_sessions:T.size,heartbeat_interval_ms:s.non_exclusive_heartbeat_interval_ms});const o=t>0?Date.now()+t:null;let r="ok",i=0;for(;!E.aborted&&T.size>=e.maxSessions&&(null===o||Date.now()<o);){const e=P();if(e.non_exclusive_heartbeat_interval_ms<=0)break;const s=ee.signal();if(r=await heartbeatActiveWorkItems(),"auth_failed"===r||"fatal"===r){s.cleanup();break}i++,await S(e.non_exclusive_heartbeat_interval_ms,s.signal),s.cleanup()}const n="auth_failed"===r||"fatal"===r?r:E.aborted?"shutdown":T.size<e.maxSessions?"capacity_changed":null!==o&&Date.now()>=o?"poll_due":"config_disabled";if(u("tengu_bridge_heartbeat_mode_exited",{reason:n,heartbeat_cycles:i,active_sessions:T.size}),"poll_due"===n&&w(`[bridge:poll] Heartbeat poll_due after ${i} cycles — falling through to pollForWork`),"auth_failed"===r||"fatal"===r){const e=ee.signal();await S(t>0?t:s.non_exclusive_heartbeat_interval_ms,e.signal),e.cleanup()}}else if(t>0){const e=ee.signal();await S(t,e.signal),e.cleanup()}}else{const e=T.size>0?s.multisession_poll_interval_ms_partial_capacity:s.multisession_poll_interval_ms_not_at_capacity;await S(e,E)}continue}const m=T.size>=e.maxSessions;if(J.has(c.id)){if(w(`[bridge:work] Skipping already-completed workId=${c.id}`),m){const e=ee.signal();s.non_exclusive_heartbeat_interval_ms>0?(await heartbeatActiveWorkItems(),await S(s.non_exclusive_heartbeat_interval_ms,e.signal)):s.multisession_poll_interval_ms_at_capacity>0&&await S(s.multisession_poll_interval_ms_at_capacity,e.signal),e.cleanup()}else await S(1e3,E);continue}let k;try{k=L(c.secret)}catch(e){const o=b(e);if(d.logError(`Failed to decode work secret for workId=${c.id}: ${o}`),u("tengu_bridge_work_secret_failed",{}),J.add(c.id),trackCleanup(stopWorkWithRetry(i,t,c.id,d,p.stopWorkBaseDelayMs)),m){const e=ee.signal();s.non_exclusive_heartbeat_interval_ms>0?(await heartbeatActiveWorkItems(),await S(s.non_exclusive_heartbeat_interval_ms,e.signal)):s.multisession_poll_interval_ms_at_capacity>0&&await S(s.multisession_poll_interval_ms_at_capacity,e.signal),e.cleanup()}continue}const ackWork=async()=>{w(`[bridge:work] Acknowledging workId=${c.id}`);try{await i.acknowledgeWork(t,c.id,k.session_ingress_token)}catch(e){w(`[bridge:work] Acknowledge failed workId=${c.id}: ${b(e)}`)}},I=c.data.type;switch(c.data.type){case"healthcheck":await ackWork(),w("[bridge:work] Healthcheck received"),d.logVerbose("Healthcheck received");break;case"session":{const s=c.data.id;try{C(s,"session_id")}catch{await ackWork(),d.logError(`Invalid session_id received: ${s}`);break}const o=T.get(s);if(o){o.updateAccessToken(k.session_ingress_token),N.set(s,k.session_ingress_token),W.set(s,c.id),te?.schedule(s,k.session_ingress_token),w(`[bridge:work] Updated access token for existing sessionId=${s} workId=${c.id}`),await ackWork();break}if(T.size>=e.maxSessions){w(`[bridge:work] At capacity (${T.size}/${e.maxSessions}), cannot spawn new session for workId=${c.id}`);break}await ackWork();const m=Date.now();let I,M,R=!1;if(!0===k.use_code_sessions||h(process.env.CONTEXT_BRIDGE_USE_CCR_V2)||h(process.env.CLAUDE_BRIDGE_USE_CCR_V2)){I=V(e.apiBaseUrl,s);for(let e=1;e<=2;e++)try{M=await z(I,k.session_ingress_token),R=!0,w(`[bridge:session] CCR v2: registered worker sessionId=${s} epoch=${M} attempt=${e}`);break}catch(o){const r=b(o);if(e<2){if(w(`[bridge:session] CCR v2: registerWorker attempt ${e} failed, retrying: ${r}`),await S(2e3,E),E.aborted)break;continue}d.logError(`CCR v2 worker registration failed for session ${s}: ${r}`),v(new Error(`registerWorker failed: ${r}`)),J.add(c.id),trackCleanup(stopWorkWithRetry(i,t,c.id,d,p.stopWorkBaseDelayMs))}if(!R)break}else I=H(e.sessionIngressUrl,s);const x=e.spawnMode;let D=e.dir,A=0;if("worktree"===x&&(void 0===g||!q(s,g))){const e=Date.now();try{const t=await $(`bridge-${F(s)}`);A=Date.now()-e,X.set(s,{worktreePath:t.worktreePath,worktreeBranch:t.worktreeBranch,gitRoot:t.gitRoot,hookBased:t.hookBased}),D=t.worktreePath,w(`[bridge:session] Created worktree for sessionId=${s} at ${t.worktreePath}`)}catch(e){const o=b(e);d.logError(`Failed to create worktree for session ${s}: ${o}`),v(new Error(`Worktree creation failed: ${o}`)),J.add(c.id),trackCleanup(stopWorkWithRetry(i,t,c.id,d,p.stopWorkBaseDelayMs));break}}w(`[bridge:session] Spawning sessionId=${s} sdkUrl=${I}`);const U=j(s),P=safeSpawn(a,{sessionId:s,sdkUrl:I,accessToken:k.session_ingress_token,useCcrV2:R,workerEpoch:M,onFirstUserMessage:s=>{if(Z.has(U))return;Z.add(U);const t=deriveSessionTitle(s);d.setSessionTitle(U,t),w(`[bridge:title] derived title for ${U}: ${t}`),import("./createSession.js").then(({updateBridgeSessionTitle:s})=>s(U,t,{baseUrl:e.apiBaseUrl})).catch(e=>w(`[bridge:title] failed to update title for ${U}: ${e}`,{level:"error"}))}},D);if("string"==typeof P){d.logError(`Failed to spawn session ${s}: ${P}`);const e=X.get(s);e&&(X.delete(s),trackCleanup(y(e.worktreePath,e.worktreeBranch,e.gitRoot,e.hookBased).catch(s=>d.logVerbose(`Failed to remove worktree ${e.worktreePath}: ${b(s)}`)))),J.add(c.id),trackCleanup(stopWorkWithRetry(i,t,c.id,d,p.stopWorkBaseDelayMs));break}const L=P,K=Date.now()-m;u("tengu_bridge_session_started",{active_sessions:T.size,spawn_mode:x,in_worktree:X.has(s),spawn_duration_ms:K,worktree_create_ms:A,inProtectedNamespace:_()}),f("info","bridge_session_started",{spawn_mode:x,in_worktree:X.has(s),spawn_duration_ms:K,worktree_create_ms:A}),T.set(s,L),W.set(s,c.id),N.set(s,k.session_ingress_token),O.set(s,U);const ee=Date.now();B.set(s,ee),d.logSessionStart(s,`Session ${s}`);const oe=F(s);let re;if(e.debugFile){const s=e.debugFile.lastIndexOf(".");re=s>0?`${e.debugFile.slice(0,s)}-${oe}${e.debugFile.slice(s)}`:`${e.debugFile}-${oe}`}else(e.verbose||"ant"===process.env.USER_TYPE)&&(re=n(r(),"claude",`bridge-session-${oe}.log`));re&&d.logVerbose(`Debug log: ${re}`),d.addSession(U,l(U,e.sessionIngressUrl)),startStatusUpdates(),d.setAttached(U),fetchSessionTitle(U,e.apiBaseUrl).then(e=>{e&&T.has(s)&&(Z.add(U),d.setSessionTitle(U,e),w(`[bridge:title] server title for ${U}: ${e}`))}).catch(e=>w(`[bridge:title] failed to fetch title for ${U}: ${e}`,{level:"error"}));const ie=e.sessionTimeoutMs??G;if(ie>0){const e=setTimeout(onSessionTimeout,ie,s,ie,d,Q,L);Y.set(s,e)}R&&se.add(s),te?.schedule(s,k.session_ingress_token),L.done.then(onSessionDone(s,ee,L));break}default:await ackWork(),w(`[bridge:work] Unknown work type: ${I}, skipping`)}if(m){const e=ee.signal();s.non_exclusive_heartbeat_interval_ms>0?(await heartbeatActiveWorkItems(),await S(s.non_exclusive_heartbeat_interval_ms,e.signal)):s.multisession_poll_interval_ms_at_capacity>0&&await S(s.multisession_poll_interval_ms_at_capacity,e.signal),e.cleanup()}}catch(e){if(E.aborted)break;if(e instanceof I){pe=!0,M(e.errorType)?d.logStatus(e.message):R(e)?w(`[bridge:work] Suppressed 403 error: ${e.message}`):(d.logError(e.message),v(e)),u("tengu_bridge_fatal_error",{status:e.status,error_type:e.errorType}),f(M(e.errorType)?"info":"error","bridge_fatal_error",{status:e.status,error_type:e.errorType});break}const s=A(e);if(isConnectionError(e)||isServerError(e)){const e=Date.now();null!==de&&e-de>pollSleepDetectionThresholdMs(p)&&(w(`[bridge:work] Detected system sleep (${Math.round((e-de)/1e3)}s gap), resetting error budget`),f("info","bridge_poll_sleep_detected",{gapMs:e-de}),ae=null,ie=0,le=null,ne=0),de=e,ae||(ae=e);const t=e-ae;if(t>=p.connGiveUpMs){d.logError(`Server unreachable for ${Math.round(t/6e4)} minutes, giving up.`),u("tengu_bridge_poll_give_up",{error_type:"connection",elapsed_ms:t}),f("error","bridge_poll_give_up",{error_type:"connection",elapsed_ms:t}),pe=!0;break}le=null,ne=0,ie=ie?Math.min(2*ie,p.connCapMs):p.connInitialMs;const o=addJitter(ie);d.logVerbose(`Connection error, retrying in ${formatDelay(o)} (${Math.round(t/1e3)}s elapsed): ${s}`),d.updateReconnectingStatus(formatDelay(o),x(t)),P().non_exclusive_heartbeat_interval_ms>0&&await heartbeatActiveWorkItems(),await S(o,E)}else{const e=Date.now();null!==de&&e-de>pollSleepDetectionThresholdMs(p)&&(w(`[bridge:work] Detected system sleep (${Math.round((e-de)/1e3)}s gap), resetting error budget`),f("info","bridge_poll_sleep_detected",{gapMs:e-de}),ae=null,ie=0,le=null,ne=0),de=e,le||(le=e);const t=e-le;if(t>=p.generalGiveUpMs){d.logError(`Persistent errors for ${Math.round(t/6e4)} minutes, giving up.`),u("tengu_bridge_poll_give_up",{error_type:"general",elapsed_ms:t}),f("error","bridge_poll_give_up",{error_type:"general",elapsed_ms:t}),pe=!0;break}ae=null,ie=0,ne=ne?Math.min(2*ne,p.generalCapMs):p.generalInitialMs;const o=addJitter(ne);d.logVerbose(`Poll failed, retrying in ${formatDelay(o)} (${Math.round(t/1e3)}s elapsed): ${s}`),d.updateReconnectingStatus(formatDelay(o),x(t)),P().non_exclusive_heartbeat_interval_ms>0&&await heartbeatActiveWorkItems(),await S(o,E)}}}stopStatusUpdates(),d.clearStatus();const ue=Date.now()-oe;u("tengu_bridge_shutdown",{active_sessions:T.size,loop_duration_ms:ue}),f("info","bridge_shutdown",{active_sessions:T.size,loop_duration_ms:ue});const ge=new Set(T.keys());g&&ge.add(g);const me=new Map(O);if(T.size>0){w(`[bridge:shutdown] Shutting down ${T.size} active session(s)`),d.logStatus(`Shutting down ${T.size} active session(s)…`);const e=new Map(W);for(const[e,s]of T.entries())w(`[bridge:shutdown] Sending SIGTERM to sessionId=${e}`),s.kill();const s=new AbortController;await Promise.race([Promise.allSettled([...T.values()].map(e=>e.done)),S(p.shutdownGraceMs??3e4,s.signal)]),s.abort();for(const[e,s]of T.entries())w(`[bridge:shutdown] Force-killing stuck sessionId=${e}`),s.forceKill();for(const e of Y.values())clearTimeout(e);if(Y.clear(),te?.cancelAll(),X.size>0){const e=[...X.values()];X.clear(),w(`[bridge:shutdown] Cleaning up ${e.length} worktree(s)`),await Promise.allSettled(e.map(e=>y(e.worktreePath,e.worktreeBranch,e.gitRoot,e.hookBased)))}await Promise.allSettled([...e.entries()].map(([e,s])=>i.stopWork(t,s,!0).catch(t=>d.logVerbose(`Failed to stop work ${s} for session ${e}: ${b(t)}`))))}if(re.size>0&&await Promise.allSettled([...re]),s("KAIROS")&&"single-session"===e.spawnMode&&g&&!pe)return d.logStatus("Resume this session by running `context remote-control --continue`"),void w(`[bridge:shutdown] Skipping archive+deregister to allow resume of session ${g}`);ge.size>0&&(w(`[bridge:shutdown] Archiving ${ge.size} session(s)`),await Promise.allSettled([...ge].map(e=>i.archiveSession(me.get(e)??j(e)).catch(s=>d.logVerbose(`Failed to archive session ${e}: ${b(s)}`)))));try{await i.deregisterEnvironment(t),w("[bridge:shutdown] Environment deregistered, bridge offline"),d.logVerbose("Environment deregistered.")}catch(e){d.logVerbose(`Failed to deregister environment: ${b(e)}`)}const{clearBridgePointer:we}=await import("./bridgePointer.js");await we(e.dir),d.logVerbose("Environment offline.")}const Y=new Set(["ECONNREFUSED","ECONNRESET","ETIMEDOUT","ENETUNREACH","EHOSTUNREACH"]);export function isConnectionError(e){return!!(e&&"object"==typeof e&&"code"in e&&"string"==typeof e.code&&Y.has(e.code))}export function isServerError(e){return!!e&&"object"==typeof e&&"code"in e&&"string"==typeof e.code&&"ERR_BAD_RESPONSE"===e.code}function addJitter(e){return Math.max(0,e+.25*e*(2*Math.random()-1))}function formatDelay(e){return e>=1e3?`${(e/1e3).toFixed(1)}s`:`${Math.round(e)}ms`}async function stopWorkWithRetry(e,s,t,o,r=1e3){for(let i=1;i<=3;i++)try{return await e.stopWork(s,t,!1),void w(`[bridge:work] stopWork succeeded for workId=${t} on attempt ${i}/3`)}catch(e){if(e instanceof I)return R(e)?w(`[bridge:work] Suppressed stopWork 403 for ${t}: ${e.message}`):o.logError(`Failed to stop work ${t}: ${e.message}`),void f("error","bridge_stop_work_failed",{attempts:i,fatal:!0});const s=b(e);if(i<3){const e=addJitter(r*Math.pow(2,i-1));o.logVerbose(`Failed to stop work ${t} (attempt ${i}/3), retrying in ${formatDelay(e)}: ${s}`),await S(e)}else o.logError(`Failed to stop work ${t} after 3 attempts: ${s}`),f("error","bridge_stop_work_failed",{attempts:3})}}function onSessionTimeout(e,s,t,o,r){w(`[bridge:session] sessionId=${e} timed out after ${x(s)}`),u("tengu_bridge_session_timeout",{timeout_ms:s}),t.logSessionFailed(e,`Session timed out after ${x(s)}`),o.add(e),r.kill()}const J=["session","same-dir","worktree"];function parseSpawnValue(e){return"session"===e?"single-session":"same-dir"===e?"same-dir":"worktree"===e?"worktree":`--spawn requires one of: ${J.join(", ")} (got: ${e??"<missing>"})`}function parseCapacityValue(e){const s=void 0===e?NaN:parseInt(e,10);return isNaN(s)||s<1?`--capacity requires a positive integer (got: ${e??"<missing>"})`:s}export function parseArgs(e){let t,o,r,i,n,l,d,c,p=!1,u=!1,g=!1,m=!1;for(let w=0;w<e.length;w++){const f=e[w];if("--help"===f||"-h"===f)g=!0;else if("--verbose"===f||"-v"===f)p=!0;else if("--sandbox"===f)u=!0;else if("--no-sandbox"===f)u=!1;else if("--debug-file"===f&&w+1<e.length)t=a(e[++w]);else if(f.startsWith("--debug-file="))t=a(f.slice(13));else if("--session-timeout"===f&&w+1<e.length)o=1e3*parseInt(e[++w],10);else if(f.startsWith("--session-timeout="))o=1e3*parseInt(f.slice(18),10);else if("--permission-mode"===f&&w+1<e.length)r=e[++w];else if(f.startsWith("--permission-mode="))r=f.slice(18);else if("--name"===f&&w+1<e.length)i=e[++w];else if(f.startsWith("--name="))i=f.slice(7);else if(s("KAIROS")&&"--session-id"===f&&w+1<e.length){if(c=e[++w],!c)return makeError("--session-id requires a value")}else if(s("KAIROS")&&f.startsWith("--session-id=")){if(c=f.slice(13),!c)return makeError("--session-id requires a value")}else if(!s("KAIROS")||"--continue"!==f&&"-c"!==f)if("--spawn"===f||f.startsWith("--spawn=")){if(void 0!==n)return makeError("--spawn may only be specified once");const s=parseSpawnValue(f.startsWith("--spawn=")?f.slice(8):e[++w]);if("single-session"!==s&&"same-dir"!==s&&"worktree"!==s)return makeError(s);n=s}else if("--capacity"===f||f.startsWith("--capacity=")){if(void 0!==l)return makeError("--capacity may only be specified once");const s=parseCapacityValue(f.startsWith("--capacity=")?f.slice(11):e[++w]);if("number"!=typeof s)return makeError(s);l=s}else if("--create-session-in-dir"===f)d=!0;else{if("--no-create-session-in-dir"!==f)return makeError(`Unknown argument: ${f}\nRun 'context remote-control --help' for usage.`);d=!1}else m=!0}return"single-session"===n&&void 0!==l?makeError("--capacity cannot be used with --spawn=session (single-session mode has fixed capacity 1)."):!c&&!m||void 0===n&&void 0===l&&void 0===d?c&&m?makeError("--session-id and --continue cannot be used together."):{verbose:p,sandbox:u,debugFile:t,sessionTimeoutMs:o,permissionMode:r,name:i,spawnMode:n,capacity:l,createSessionInDir:d,sessionId:c,continueSession:m,help:g}:makeError("--session-id and --continue cannot be used with --spawn, --capacity, or --create-session-in-dir.");function makeError(e){return{verbose:p,sandbox:u,debugFile:t,sessionTimeoutMs:o,permissionMode:r,name:i,spawnMode:n,capacity:l,createSessionInDir:d,sessionId:c,continueSession:m,help:g,error:e}}}const X=80;function deriveSessionTitle(e){const s=e.replace(/\s+/g," ").trim();return k(s,X)}async function fetchSessionTitle(e,s){const{getBridgeSession:t}=await import("./createSession.js"),o=await t(e,{baseUrl:s});return o?.title||void 0}export async function bridgeMain(r){const n=parseArgs(r);if(n.help)return void await async function(){const{EXTERNAL_PERMISSION_MODES:e}=await import("../types/permissions.js"),t=e.join(", "),o=await isMultiSessionSpawnEnabled(),r=o?" --spawn <mode> Spawn mode: same-dir, worktree, session\n (default: same-dir)\n --capacity <N> Max concurrent sessions in worktree or\n same-dir mode (default: 32)\n --[no-]create-session-in-dir Pre-create a session in the current\n directory; in worktree mode this session\n stays in cwd while on-demand sessions get\n isolated worktrees (default: on)\n":"",i=o?"\n Remote Control runs as a persistent server that accepts multiple concurrent\n sessions in the current directory. One session is pre-created on start so\n you have somewhere to type immediately. Use --spawn=worktree to isolate\n each on-demand session in its own git worktree, or --spawn=session for\n the classic single-session mode (exits when that session ends). Press 'w'\n during runtime to toggle between same-dir and worktree.\n":"",n=o?" - Worktree mode requires a git repository or WorktreeCreate/WorktreeRemove hooks\n":"",a=`\nRemote Control - Connect your local environment to claude.ai/code\n\nUSAGE\n context remote-control [options]\nOPTIONS\n --name <name> Name for the session (shown in claude.ai/code)\n${s("KAIROS")?" -c, --continue Resume the last session in this directory\n --session-id <id> Resume a specific session by ID (cannot be\n used with spawn flags or --continue)\n":""} --permission-mode <mode> Permission mode for spawned sessions\n (${t})\n --debug-file <path> Write debug logs to file\n -v, --verbose Enable verbose output\n -h, --help Show this help\n${r}\nDESCRIPTION\n Remote Control allows you to control sessions on your local device from\n claude.ai/code (https://claude.ai/code). Run this command in the\n directory you want to work in, then connect from the Claude app or web.\n${i}\nNOTES\n - You must be logged in with a Claude account that has a subscription\n - Run \`context\` first in the directory to accept the workspace trust dialog\n${n}`;console.log(a)}();n.error&&(console.error(`Error: ${n.error}`),process.exit(1));const{verbose:l,sandbox:p,debugFile:m,sessionTimeoutMs:h,permissionMode:_,name:k,spawnMode:$,capacity:y,createSessionInDir:M,sessionId:R,continueSession:x}=n;let D,A=R;const U=void 0!==$||void 0!==y||void 0!==M;if(void 0!==_){const{PERMISSION_MODES:e}=await import("../types/permissions.js"),s=e;s.includes(_)||(console.error(`Error: Invalid permission mode '${_}'. Valid modes: ${s.join(", ")}`),process.exit(1))}const j=a("."),{enableConfigs:F,checkHasTrustDialogAccepted:G}=await import("../utils/config.js");F();const{initSinks:V}=await import("../utils/sinks.js");V();const H=await isMultiSessionSpawnEnabled();U&&!H&&(await g("tengu_bridge_multi_session_denied",{used_spawn:void 0!==$,used_capacity:void 0!==y,used_create_session_in_dir:void 0!==M}),await Promise.race([Promise.all([c(),d()]),S(500,void 0,{unref:!0})]).catch(()=>{}),console.error("Error: Multi-session Remote Control is not enabled for your account yet."),process.exit(1));const{setOriginalCwd:L,setCwdState:z}=await import("../bootstrap/state.js");L(j),z(j),G()||(console.error(`Error: Workspace not trusted. Please run \`context\` in ${j} first to review and accept the workspace trust dialog.`),process.exit(1));const{clearOAuthTokenCache:q,checkAndRefreshOAuthTokenIfNeeded:K}=await import("../utils/auth.js"),{getBridgeAccessToken:Y,getBridgeBaseUrl:J}=await import("./bridgeConfig.js");Y()||(console.error(N),process.exit(1));const{getGlobalConfig:X,saveGlobalConfig:Q,getCurrentProjectConfig:Z,saveCurrentProjectConfig:ee}=await import("../utils/config.js");if(!X().remoteDialogSeen){const e=(await import("readline")).createInterface({input:process.stdin,output:process.stdout});console.log("\nRemote Control lets you access this CLI session from the web (claude.ai/code)\nor the Claude app, so you can pick up where you left off on any device.\n\nYou can disconnect remote access anytime by running /remote-control again.\n");const s=await new Promise(s=>{e.question("Enable Remote Control? (y/n) ",s)});e.close(),Q(e=>e.remoteDialogSeen?e:{...e,remoteDialogSeen:!0}),"y"!==s.toLowerCase()&&"yes"!==s.toLowerCase()&&process.exit(0)}if(s("KAIROS")&&x){const{readBridgePointerAcrossWorktrees:e}=await import("./bridgePointer.js"),s=await e(j);s||(console.error("Error: No recent session found in this directory or its worktrees. Run `context remote-control` to start a new one."),process.exit(1));const{pointer:t,dir:o}=s,r=Math.round(t.ageMs/6e4),i=r<60?`${r}m`:`${Math.round(r/60)}h`,n=o!==j?` from worktree ${o}`:"";console.error(`Resuming session ${t.sessionId} (${i} ago)${n}…`),A=t.sessionId,D=o}const se=J();!se.startsWith("http://")||se.includes("localhost")||se.includes("127.0.0.1")||(console.error("Error: Remote Control base URL uses HTTP. Only HTTPS or localhost HTTP is allowed."),process.exit(1));const te="ant"===process.env.USER_TYPE?process.env.CONTEXT_BRIDGE_SESSION_INGRESS_URL??process.env.CLAUDE_BRIDGE_SESSION_INGRESS_URL??se:se,{getBranch:oe,getRemoteUrl:re,findGitRoot:ie}=await import("../utils/git.js"),{hasWorktreeCreateHook:ne}=await import("../utils/hooks.js"),ae=ne()||null!==ie(j);let le,de,ce=H?Z().remoteControlSpawnMode:void 0;if("worktree"!==ce||ae||(console.error("Warning: Saved spawn mode is worktree but this directory is not a git repository. Falling back to same-dir."),ce=void 0,ee(e=>void 0===e.remoteControlSpawnMode?e:{...e,remoteControlSpawnMode:void 0})),H&&!ce&&ae&&void 0===$&&!A&&process.stdin.isTTY){const e=(await import("readline")).createInterface({input:process.stdin,output:process.stdout});console.log("\nClaude Remote Control is launching in spawn mode which lets you create new sessions in this project from Context Code on Web or your Mobile app. Learn more here: https://code.claude.com/docs/en/remote-control\n\nSpawn mode for this project:\n [1] same-dir — sessions share the current directory (default)\n [2] worktree — each session gets an isolated git worktree\n\nThis can be changed later or explicitly set with --spawn=same-dir or --spawn=worktree.\n");const s=await new Promise(s=>{e.question("Choose [1/2] (default: 1): ",s)});e.close();const t="2"===s.trim()?"worktree":"same-dir";ce=t,u("tengu_bridge_spawn_mode_chosen",{spawn_mode:t}),ee(e=>e.remoteControlSpawnMode===t?e:{...e,remoteControlSpawnMode:t})}A?(de="single-session",le="resume"):void 0!==$?(de=$,le="flag"):void 0!==ce?(de=ce,le="saved"):(de=H?"same-dir":"single-session",le="gate_default");const pe="single-session"===de?1:y??32,ue=M??!0;if(!A){const{clearBridgePointer:e}=await import("./bridgePointer.js");await e(j)}"worktree"!==de||ae||(console.error("Error: Worktree mode requires a git repository or WorktreeCreate hooks configured. Use --spawn=session for single-session mode."),process.exit(1));const ge=await oe(),me=await re(),we=o(),fe=t(),{handleOAuth401Error:he}=await import("../utils/auth.js"),_e=E({baseUrl:se,getAccessToken:Y,runnerVersion:e.VERSION,onDebug:w,onAuth401:he,getTrustedDeviceToken:O});let be;if(s("KAIROS")&&A){try{C(A,"sessionId")}catch{console.error(`Error: Invalid session ID "${A}". Session IDs must not contain unsafe characters.`),process.exit(1)}await K(),q();const{getBridgeSession:e}=await import("./createSession.js"),s=await e(A,{baseUrl:se,getAccessToken:Y});if(!s){if(D){const{clearBridgePointer:e}=await import("./bridgePointer.js");await e(D)}console.error(`Error: Session ${A} not found. It may have been archived or expired, or your login may have lapsed (run \`context /login\`).`),process.exit(1)}if(!s.environment_id){if(D){const{clearBridgePointer:e}=await import("./bridgePointer.js");await e(D)}console.error(`Error: Session ${A} has no environment_id. It may never have been attached to a bridge.`),process.exit(1)}be=s.environment_id,w(`[bridge:init] Resuming session ${A} on environment ${be}`)}const ke={dir:j,machineName:we,branch:ge,gitRepoUrl:me,maxSessions:pe,spawnMode:de,verbose:l,sandbox:p,bridgeId:fe,workerType:"claude_code",environmentId:t(),reuseEnvironmentId:be,apiBaseUrl:se,sessionIngressUrl:te,debugFile:m,sessionTimeoutMs:h};let ve,Se,$e;w(`[bridge:init] bridgeId=${fe}${be?` reuseEnvironmentId=${be}`:""} dir=${j} branch=${ge} gitRepoUrl=${me} machine=${we}`),w(`[bridge:init] apiBaseUrl=${se} sessionIngressUrl=${te}`),w(`[bridge:init] sandbox=${p}${m?` debugFile=${m}`:""}`);try{const e=await _e.registerBridgeEnvironment(ke);ve=e.environment_id,Se=e.environment_secret}catch(e){u("tengu_bridge_registration_failed",{status:e instanceof I?e.status:void 0}),console.error(e instanceof I&&404===e.status?"Remote Control environments are not available for your account.":`Error: ${b(e)}`),process.exit(1)}if(s("KAIROS")&&A)if(be&&ve!==be)v(new Error(`Bridge resume env mismatch: requested ${be}, backend returned ${ve}. Falling back to fresh session.`)),console.warn(`Warning: Could not resume session ${A} — its environment has expired. Creating a fresh session instead.`);else{const e=B(A),s=e===A?[A]:[A,e];let t,o=!1;for(const e of s)try{await _e.reconnectSession(ve,e),w(`[bridge:init] Session ${e} re-queued via bridge/reconnect`),$e=A,o=!0;break}catch(s){t=s,w(`[bridge:init] reconnectSession(${e}) failed: ${b(s)}`)}if(!o){const e=t,s=e instanceof I;if(D&&s){const{clearBridgePointer:e}=await import("./bridgePointer.js");await e(D)}console.error(s?`Error: ${b(e)}`:`Error: Failed to reconnect session ${A}: ${b(e)}\nThe session may still be resumable — try running the same command again.`),process.exit(1)}}w(`[bridge:init] Registered, server environmentId=${ve}`);const ye=P();u("tengu_bridge_started",{max_sessions:ke.maxSessions,has_debug_file:!!ke.debugFile,sandbox:ke.sandbox,verbose:ke.verbose,heartbeat_interval_ms:ye.non_exclusive_heartbeat_interval_ms,spawn_mode:ke.spawnMode,spawn_mode_source:le,multi_session_gate:H,pre_create_session:ue,worktree_available:ae}),f("info","bridge_started",{max_sessions:ke.maxSessions,sandbox:ke.sandbox,spawn_mode:ke.spawnMode});const Ie=W({execPath:process.execPath,scriptArgs:spawnScriptArgs(),env:process.env,verbose:l,sandbox:p,debugFile:m,permissionMode:_,onDebug:w,onActivity:(e,s)=>{w(`[bridge:activity] sessionId=${e} ${s.type} ${s.summary}`)},onPermissionRequest:(e,s,t)=>{w(`[bridge:perm] sessionId=${e} tool=${s.request.tool_name} request_id=${s.request_id} (not auto-approving)`)}}),Ee=T({verbose:l}),{parseGitHubRepository:Me}=await import("../utils/detectRepository.js"),Re=me?Me(me):null,Ce=Re?Re.split("/").pop():i(j);Ee.setRepoInfo(Ce,ge);const xe="single-session"!==de&&ae;xe&&Ee.setSpawnModeDisplay(de);const onStdinData=e=>{if(3!==e[0]&&4!==e[0])if(32!==e[0]){if(119===e[0]){if(!xe)return;const e="same-dir"===ke.spawnMode?"worktree":"same-dir";return ke.spawnMode=e,u("tengu_bridge_spawn_mode_toggled",{spawn_mode:e}),Ee.logStatus("worktree"===e?"Spawn mode: worktree (new sessions get isolated git worktrees)":"Spawn mode: same-dir (new sessions share the current directory)"),Ee.setSpawnModeDisplay(e),Ee.refreshDisplay(),void ee(s=>s.remoteControlSpawnMode===e?s:{...s,remoteControlSpawnMode:e})}}else Ee.toggleQr();else process.emit("SIGINT")};process.stdin.isTTY&&(process.stdin.setRawMode(!0),process.stdin.resume(),process.stdin.on("data",onStdinData));const Te=new AbortController,onSigint=()=>{w("[bridge:shutdown] SIGINT received, shutting down"),Te.abort()},onSigterm=()=>{w("[bridge:shutdown] SIGTERM received, shutting down"),Te.abort()};process.on("SIGINT",onSigint),process.on("SIGTERM",onSigterm);let De=s("KAIROS")&&$e?$e:null;if(ue&&(!s("KAIROS")||!$e)){const{createBridgeSession:e}=await import("./createSession.js");try{De=await e({environmentId:ve,title:k,events:[],gitRepoUrl:me,branch:ge,signal:Te.signal,baseUrl:se,getAccessToken:Y,permissionMode:_}),De&&w(`[bridge:init] Created initial session ${De}`)}catch(e){w(`[bridge:init] Session creation failed (non-fatal): ${b(e)}`)}}let Ae=null;if(De&&"single-session"===de){const{writeBridgePointer:e}=await import("./bridgePointer.js"),s={sessionId:De,environmentId:ve,source:"standalone"};await e(ke.dir,s),Ae=setInterval(e,36e5,ke.dir,s),Ae.unref?.()}try{await runBridgeLoop(ke,ve,Se,_e,Ie,Ee,Te.signal,void 0,De??void 0,async()=>(q(),await K(),Y()))}finally{null!==Ae&&clearInterval(Ae),process.off("SIGINT",onSigint),process.off("SIGTERM",onSigterm),process.stdin.off("data",onStdinData),process.stdin.isTTY&&process.stdin.setRawMode(!1),process.stdin.pause()}process.exit(0)}export class BridgeHeadlessPermanentError extends Error{constructor(e){super(e),this.name="BridgeHeadlessPermanentError"}}export async function runBridgeHeadless(s,r){const{dir:i,log:n}=s;process.chdir(i);const{setOriginalCwd:a,setCwdState:l}=await import("../bootstrap/state.js");a(i),l(i);const{enableConfigs:d,checkHasTrustDialogAccepted:c}=await import("../utils/config.js");d();const{initSinks:p}=await import("../utils/sinks.js");if(p(),!c())throw new BridgeHeadlessPermanentError(`Workspace not trusted: ${i}. Run \`context\` in that directory first to accept the trust dialog.`);if(!s.getAccessToken())throw new Error(N);const{getBridgeBaseUrl:u}=await import("./bridgeConfig.js"),g=u();if(g.startsWith("http://")&&!g.includes("localhost")&&!g.includes("127.0.0.1"))throw new BridgeHeadlessPermanentError("Remote Control base URL uses HTTP. Only HTTPS or localhost HTTP is allowed.");const m="ant"===process.env.USER_TYPE?process.env.CONTEXT_BRIDGE_SESSION_INGRESS_URL??process.env.CLAUDE_BRIDGE_SESSION_INGRESS_URL??g:g,{getBranch:w,getRemoteUrl:f,findGitRoot:h}=await import("../utils/git.js"),{hasWorktreeCreateHook:_}=await import("../utils/hooks.js");if("worktree"===s.spawnMode){if(!(_()||null!==h(i)))throw new BridgeHeadlessPermanentError(`Worktree mode requires a git repository or WorktreeCreate hooks. Directory ${i} has neither.`)}const k=await w(),v=await f(),S=o(),$=t(),y={dir:i,machineName:S,branch:k,gitRepoUrl:v,maxSessions:s.capacity,spawnMode:s.spawnMode,verbose:!1,sandbox:s.sandbox,bridgeId:$,workerType:"claude_code",environmentId:t(),apiBaseUrl:g,sessionIngressUrl:m,sessionTimeoutMs:s.sessionTimeoutMs},I=E({baseUrl:g,getAccessToken:s.getAccessToken,runnerVersion:e.VERSION,onDebug:n,onAuth401:s.onAuth401,getTrustedDeviceToken:O});let M,R;try{const e=await I.registerBridgeEnvironment(y);M=e.environment_id,R=e.environment_secret}catch(e){throw new Error(`Bridge registration failed: ${b(e)}`)}const C=W({execPath:process.execPath,scriptArgs:spawnScriptArgs(),env:process.env,verbose:!1,sandbox:s.sandbox,permissionMode:s.permissionMode,onDebug:n}),x=function(e){const noop=()=>{};return{printBanner:(s,t)=>e(`registered environmentId=${t} dir=${s.dir} spawnMode=${s.spawnMode} capacity=${s.maxSessions}`),logSessionStart:(s,t)=>e(`session start ${s}`),logSessionComplete:(s,t)=>e(`session complete ${s} (${t}ms)`),logSessionFailed:(s,t)=>e(`session failed ${s}: ${t}`),logStatus:e,logVerbose:e,logError:s=>e(`error: ${s}`),logReconnected:s=>e(`reconnected after ${s}ms`),addSession:(s,t)=>e(`session attached ${s}`),removeSession:s=>e(`session detached ${s}`),updateIdleStatus:noop,updateReconnectingStatus:noop,updateSessionStatus:noop,updateSessionActivity:noop,updateSessionCount:noop,updateFailedStatus:noop,setSpawnModeDisplay:noop,setRepoInfo:noop,setDebugLogPath:noop,setAttached:noop,setSessionTitle:noop,clearStatus:noop,toggleQr:noop,refreshDisplay:noop}}(n);let T;if(x.printBanner(y,M),s.createSessionOnStart){const{createBridgeSession:e}=await import("./createSession.js");try{const t=await e({environmentId:M,title:s.name,events:[],gitRepoUrl:v,branch:k,signal:r,baseUrl:g,getAccessToken:s.getAccessToken,permissionMode:s.permissionMode});t&&(T=t,n(`created initial session ${t}`))}catch(e){n(`session pre-creation failed (non-fatal): ${b(e)}`)}}await runBridgeLoop(y,M,R,I,C,x,r,void 0,T,async()=>s.getAccessToken())}
|
|
1
|
+
import{MACRO as e,feature as s}from"../recovery/bunBundleShim.js";import{randomUUID as t}from"crypto";import{hostname as o,tmpdir as r}from"os";import{basename as i,join as n,resolve as a}from"path";import{getRemoteSessionUrl as l}from"../constants/product.js";import{shutdownDatadog as d}from"../services/analytics/datadog.js";import{shutdown1PEventLogging as c}from"../services/analytics/firstPartyEventLogger.js";import{checkGate_CACHED_OR_BLOCKING as p}from"../services/analytics/growthbook.js";import{logEvent as u,logEventAsync as g}from"../services/analytics/index.js";import{isInBundledMode as m}from"../utils/bundledMode.js";import{logForDebugging as w}from"../utils/debug.js";import{logForDiagnosticsNoPII as f}from"../utils/diagLogs.js";import{isEnvTruthy as _,isInProtectedNamespace as h}from"../utils/envUtils.js";import{errorMessage as b}from"../utils/errors.js";import{truncateToWidth as k}from"../utils/format.js";import{logError as v}from"../utils/log.js";import{sleep as S}from"../utils/sleep.js";import{createAgentWorktree as $,removeAgentWorktree as y}from"../utils/worktree.js";import{BridgeFatalError as I,createBridgeApiClient as E,isExpiredErrorType as M,isSuppressible403 as R,validateBridgeId as C}from"./bridgeApi.js";import{formatDuration as x}from"./bridgeStatusUtil.js";import{createBridgeLogger as T}from"./bridgeUI.js";import{createCapacityWake as D}from"./capacityWake.js";import{describeAxiosError as A}from"./debugUtils.js";import{createTokenRefreshScheduler as U}from"./jwtUtils.js";import{getPollIntervalConfig as P}from"./pollConfig.js";import{toCompatSessionId as j,toInfraSessionId as B}from"./sessionIdCompat.js";import{createSessionSpawner as W,safeFilenameId as F}from"./sessionRunner.js";import{getTrustedDeviceToken as O}from"./trustedDevice.js";import{BRIDGE_LOGIN_ERROR as N,DEFAULT_SESSION_TIMEOUT_MS as G}from"./types.js";import{buildCCRv2SdkUrl as V,buildSdkUrl as H,decodeWorkSecret as L,registerWorker as z,sameSessionId as q}from"./workSecret.js";const K={connInitialMs:2e3,connCapMs:12e4,connGiveUpMs:6e5,generalInitialMs:500,generalCapMs:3e4,generalGiveUpMs:6e5};async function isMultiSessionSpawnEnabled(){return p("tengu_ccr_bridge_multi_session")}function pollSleepDetectionThresholdMs(e){return 2*e.connCapMs}function spawnScriptArgs(){return m()||!process.argv[1]?[]:[process.argv[1]]}function safeSpawn(e,s,t){try{return e.spawn(s,t)}catch(e){const s=b(e);return v(new Error(`Session spawn failed: ${s}`)),s}}export async function runBridgeLoop(e,t,o,i,a,d,c,p=K,g,m){const k=new AbortController;c.aborted?k.abort():c.addEventListener("abort",()=>k.abort(),{once:!0});const E=k.signal,T=new Map,B=new Map,W=new Map,O=new Map,N=new Map,Y=new Map,J=new Set,X=new Map,Q=new Set,Z=new Set,ee=D(E);async function heartbeatActiveWorkItems(){let e=!1,s=!1;const o=[];for(const[r]of T){const n=W.get(r),a=N.get(r);if(n&&a)try{await i.heartbeatWork(t,n,a),e=!0}catch(e){w(`[bridge:heartbeat] Failed for sessionId=${r} workId=${n}: ${b(e)}`),e instanceof I&&(u("tengu_bridge_heartbeat_error",{status:e.status,error_type:401===e.status||403===e.status?"auth_failed":"fatal"}),401===e.status||403===e.status?o.push(r):s=!0)}}for(const e of o){d.logVerbose(`Session ${e} token expired — re-queuing via bridge/reconnect`);try{await i.reconnectSession(t,e),w(`[bridge:heartbeat] Re-queued sessionId=${e} via bridge/reconnect`)}catch(s){d.logError(`Failed to refresh session ${e} token: ${b(s)}`),w(`[bridge:heartbeat] reconnectSession(${e}) failed: ${b(s)}`,{level:"error"})}}return s?"fatal":o.length>0?"auth_failed":e?"ok":"failed"}const se=new Set,te=m?U({getAccessToken:m,onRefresh:(e,s)=>{const o=T.get(e);o&&(se.has(e)?(d.logVerbose(`Refreshing session ${e} token via bridge/reconnect`),i.reconnectSession(t,e).catch(s=>{d.logError(`Failed to refresh session ${e} token: ${b(s)}`),w(`[bridge:token] reconnectSession(${e}) failed: ${b(s)}`,{level:"error"})})):o.updateAccessToken(s))},label:"bridge"}):null,oe=Date.now(),re=new Set;function trackCleanup(e){re.add(e),e.finally(()=>re.delete(e))}let ie=0,ne=0,ae=null,le=null,de=null,ce=null,pe=!1;if(w(`[bridge:work] Starting poll loop spawnMode=${e.spawnMode} maxSessions=${e.maxSessions} environmentId=${t}`),f("info","bridge_loop_started",{max_sessions:e.maxSessions,spawn_mode:e.spawnMode}),"ant"===process.env.USER_TYPE){let s;if(e.debugFile){const t=e.debugFile.lastIndexOf(".");s=t>0?`${e.debugFile.slice(0,t)}-*${e.debugFile.slice(t)}`:`${e.debugFile}-*`}else s=n(r(),"claude","bridge-session-*.log");d.setDebugLogPath(s)}function updateStatusDisplay(){d.updateSessionCount(T.size,e.maxSessions,e.spawnMode);for(const[e,s]of T){const t=s.currentActivity;t&&d.updateSessionActivity(O.get(e)??e,t)}if(0===T.size)return void d.updateIdleStatus();const[s,t]=[...T.entries()].pop(),o=B.get(s);if(!o)return;const r=t.currentActivity;if(!r||"result"===r.type||"error"===r.type)return void(e.maxSessions>1&&d.refreshDisplay());const i=x(Date.now()-o),n=t.activities.filter(e=>"tool_start"===e.type).slice(-5).map(e=>e.summary);d.updateSessionStatus(s,i,r,n)}function startStatusUpdates(){stopStatusUpdates(),updateStatusDisplay(),ce=setInterval(updateStatusDisplay,1e3)}function stopStatusUpdates(){ce&&(clearInterval(ce),ce=null)}function onSessionDone(s,o,r){return n=>{const a=W.get(s);T.delete(s),B.delete(s),W.delete(s),N.delete(s);const l=O.get(s)??s;O.delete(s),d.removeSession(l),Z.delete(l),se.delete(s);const c=Y.get(s);c&&(clearTimeout(c),Y.delete(s)),te?.cancel(s),ee.wake();const g=Q.delete(s),m=g&&"interrupted"===n?"failed":n,_=Date.now()-o;w(`[bridge:session] sessionId=${s} workId=${a??"unknown"} exited status=${m} duration=${x(_)}`),u("tengu_bridge_session_done",{status:m,duration_ms:_}),f("info","bridge_session_done",{status:m,duration_ms:_}),d.clearStatus(),stopStatusUpdates();const h=r.lastStderr.length>0?r.lastStderr.join("\n"):void 0;let S;switch(m){case"completed":d.logSessionComplete(s,_);break;case"failed":g||E.aborted||(S=h??"Process exited with error",d.logSessionFailed(s,S),v(new Error(`Bridge session failed: ${S}`)));break;case"interrupted":d.logVerbose(`Session ${s} interrupted`)}"interrupted"!==m&&a&&(trackCleanup(stopWorkWithRetry(i,t,a,d,p.stopWorkBaseDelayMs)),J.add(a));const $=X.get(s);if($&&(X.delete(s),trackCleanup(y($.worktreePath,$.worktreeBranch,$.gitRoot,$.hookBased).catch(e=>d.logVerbose(`Failed to remove worktree ${$.worktreePath}: ${b(e)}`)))),"interrupted"!==m&&!E.aborted){if("single-session"===e.spawnMode)return w(`[bridge:session] Session ${m}, aborting poll loop to tear down environment`),void k.abort();trackCleanup(i.archiveSession(l).catch(e=>d.logVerbose(`Failed to archive session ${s}: ${b(e)}`))),w(`[bridge:session] Session ${m}, returning to idle (multi-session mode)`)}E.aborted||startStatusUpdates()}}for(d.printBanner(e,t),d.updateSessionCount(0,e.maxSessions,e.spawnMode),g&&d.setAttached(g),g||startStatusUpdates();!E.aborted;){const s=P();try{const c=await i.pollForWork(t,o,E,s.reclaim_older_than_ms);if(null!==ae||null!==le){const e=Date.now()-(ae??le??Date.now());d.logReconnected(e),w(`[bridge:poll] Reconnected after ${x(e)}`),u("tengu_bridge_reconnected",{disconnected_ms:e})}if(ie=0,ne=0,ae=null,le=null,de=null,!c){if(T.size>=e.maxSessions){const t=s.multisession_poll_interval_ms_at_capacity;if(s.non_exclusive_heartbeat_interval_ms>0){u("tengu_bridge_heartbeat_mode_entered",{active_sessions:T.size,heartbeat_interval_ms:s.non_exclusive_heartbeat_interval_ms});const o=t>0?Date.now()+t:null;let r="ok",i=0;for(;!E.aborted&&T.size>=e.maxSessions&&(null===o||Date.now()<o);){const e=P();if(e.non_exclusive_heartbeat_interval_ms<=0)break;const s=ee.signal();if(r=await heartbeatActiveWorkItems(),"auth_failed"===r||"fatal"===r){s.cleanup();break}i++,await S(e.non_exclusive_heartbeat_interval_ms,s.signal),s.cleanup()}const n="auth_failed"===r||"fatal"===r?r:E.aborted?"shutdown":T.size<e.maxSessions?"capacity_changed":null!==o&&Date.now()>=o?"poll_due":"config_disabled";if(u("tengu_bridge_heartbeat_mode_exited",{reason:n,heartbeat_cycles:i,active_sessions:T.size}),"poll_due"===n&&w(`[bridge:poll] Heartbeat poll_due after ${i} cycles — falling through to pollForWork`),"auth_failed"===r||"fatal"===r){const e=ee.signal();await S(t>0?t:s.non_exclusive_heartbeat_interval_ms,e.signal),e.cleanup()}}else if(t>0){const e=ee.signal();await S(t,e.signal),e.cleanup()}}else{const e=T.size>0?s.multisession_poll_interval_ms_partial_capacity:s.multisession_poll_interval_ms_not_at_capacity;await S(e,E)}continue}const m=T.size>=e.maxSessions;if(J.has(c.id)){if(w(`[bridge:work] Skipping already-completed workId=${c.id}`),m){const e=ee.signal();s.non_exclusive_heartbeat_interval_ms>0?(await heartbeatActiveWorkItems(),await S(s.non_exclusive_heartbeat_interval_ms,e.signal)):s.multisession_poll_interval_ms_at_capacity>0&&await S(s.multisession_poll_interval_ms_at_capacity,e.signal),e.cleanup()}else await S(1e3,E);continue}let k;try{k=L(c.secret)}catch(e){const o=b(e);if(d.logError(`Failed to decode work secret for workId=${c.id}: ${o}`),u("tengu_bridge_work_secret_failed",{}),J.add(c.id),trackCleanup(stopWorkWithRetry(i,t,c.id,d,p.stopWorkBaseDelayMs)),m){const e=ee.signal();s.non_exclusive_heartbeat_interval_ms>0?(await heartbeatActiveWorkItems(),await S(s.non_exclusive_heartbeat_interval_ms,e.signal)):s.multisession_poll_interval_ms_at_capacity>0&&await S(s.multisession_poll_interval_ms_at_capacity,e.signal),e.cleanup()}continue}const ackWork=async()=>{w(`[bridge:work] Acknowledging workId=${c.id}`);try{await i.acknowledgeWork(t,c.id,k.session_ingress_token)}catch(e){w(`[bridge:work] Acknowledge failed workId=${c.id}: ${b(e)}`)}},I=c.data.type;switch(c.data.type){case"healthcheck":await ackWork(),w("[bridge:work] Healthcheck received"),d.logVerbose("Healthcheck received");break;case"session":{const s=c.data.id;try{C(s,"session_id")}catch{await ackWork(),d.logError(`Invalid session_id received: ${s}`);break}const o=T.get(s);if(o){o.updateAccessToken(k.session_ingress_token),N.set(s,k.session_ingress_token),W.set(s,c.id),te?.schedule(s,k.session_ingress_token),w(`[bridge:work] Updated access token for existing sessionId=${s} workId=${c.id}`),await ackWork();break}if(T.size>=e.maxSessions){w(`[bridge:work] At capacity (${T.size}/${e.maxSessions}), cannot spawn new session for workId=${c.id}`);break}await ackWork();const m=Date.now();let I,M,R=!1;if(!0===k.use_code_sessions||_(process.env.CONTEXT_BRIDGE_USE_CCR_V2)||_(process.env.CLAUDE_BRIDGE_USE_CCR_V2)){I=V(e.apiBaseUrl,s);for(let e=1;e<=2;e++)try{M=await z(I,k.session_ingress_token),R=!0,w(`[bridge:session] CCR v2: registered worker sessionId=${s} epoch=${M} attempt=${e}`);break}catch(o){const r=b(o);if(e<2){if(w(`[bridge:session] CCR v2: registerWorker attempt ${e} failed, retrying: ${r}`),await S(2e3,E),E.aborted)break;continue}d.logError(`CCR v2 worker registration failed for session ${s}: ${r}`),v(new Error(`registerWorker failed: ${r}`)),J.add(c.id),trackCleanup(stopWorkWithRetry(i,t,c.id,d,p.stopWorkBaseDelayMs))}if(!R)break}else I=H(e.sessionIngressUrl,s);const x=e.spawnMode;let D=e.dir,A=0;if("worktree"===x&&(void 0===g||!q(s,g))){const e=Date.now();try{const t=await $(`bridge-${F(s)}`);A=Date.now()-e,X.set(s,{worktreePath:t.worktreePath,worktreeBranch:t.worktreeBranch,gitRoot:t.gitRoot,hookBased:t.hookBased}),D=t.worktreePath,w(`[bridge:session] Created worktree for sessionId=${s} at ${t.worktreePath}`)}catch(e){const o=b(e);d.logError(`Failed to create worktree for session ${s}: ${o}`),v(new Error(`Worktree creation failed: ${o}`)),J.add(c.id),trackCleanup(stopWorkWithRetry(i,t,c.id,d,p.stopWorkBaseDelayMs));break}}w(`[bridge:session] Spawning sessionId=${s} sdkUrl=${I}`);const U=j(s),P=safeSpawn(a,{sessionId:s,sdkUrl:I,accessToken:k.session_ingress_token,useCcrV2:R,workerEpoch:M,onFirstUserMessage:s=>{if(Z.has(U))return;Z.add(U);const t=deriveSessionTitle(s);d.setSessionTitle(U,t),w(`[bridge:title] derived title for ${U}: ${t}`),import("./createSession.js").then(({updateBridgeSessionTitle:s})=>s(U,t,{baseUrl:e.apiBaseUrl})).catch(e=>w(`[bridge:title] failed to update title for ${U}: ${e}`,{level:"error"}))}},D);if("string"==typeof P){d.logError(`Failed to spawn session ${s}: ${P}`);const e=X.get(s);e&&(X.delete(s),trackCleanup(y(e.worktreePath,e.worktreeBranch,e.gitRoot,e.hookBased).catch(s=>d.logVerbose(`Failed to remove worktree ${e.worktreePath}: ${b(s)}`)))),J.add(c.id),trackCleanup(stopWorkWithRetry(i,t,c.id,d,p.stopWorkBaseDelayMs));break}const L=P,K=Date.now()-m;u("tengu_bridge_session_started",{active_sessions:T.size,spawn_mode:x,in_worktree:X.has(s),spawn_duration_ms:K,worktree_create_ms:A,inProtectedNamespace:h()}),f("info","bridge_session_started",{spawn_mode:x,in_worktree:X.has(s),spawn_duration_ms:K,worktree_create_ms:A}),T.set(s,L),W.set(s,c.id),N.set(s,k.session_ingress_token),O.set(s,U);const ee=Date.now();B.set(s,ee),d.logSessionStart(s,`Session ${s}`);const oe=F(s);let re;if(e.debugFile){const s=e.debugFile.lastIndexOf(".");re=s>0?`${e.debugFile.slice(0,s)}-${oe}${e.debugFile.slice(s)}`:`${e.debugFile}-${oe}`}else(e.verbose||"ant"===process.env.USER_TYPE)&&(re=n(r(),"claude",`bridge-session-${oe}.log`));re&&d.logVerbose(`Debug log: ${re}`),d.addSession(U,l(U,e.sessionIngressUrl)),startStatusUpdates(),d.setAttached(U),fetchSessionTitle(U,e.apiBaseUrl).then(e=>{e&&T.has(s)&&(Z.add(U),d.setSessionTitle(U,e),w(`[bridge:title] server title for ${U}: ${e}`))}).catch(e=>w(`[bridge:title] failed to fetch title for ${U}: ${e}`,{level:"error"}));const ie=e.sessionTimeoutMs??G;if(ie>0){const e=setTimeout(onSessionTimeout,ie,s,ie,d,Q,L);Y.set(s,e)}R&&se.add(s),te?.schedule(s,k.session_ingress_token),L.done.then(onSessionDone(s,ee,L));break}default:await ackWork(),w(`[bridge:work] Unknown work type: ${I}, skipping`)}if(m){const e=ee.signal();s.non_exclusive_heartbeat_interval_ms>0?(await heartbeatActiveWorkItems(),await S(s.non_exclusive_heartbeat_interval_ms,e.signal)):s.multisession_poll_interval_ms_at_capacity>0&&await S(s.multisession_poll_interval_ms_at_capacity,e.signal),e.cleanup()}}catch(e){if(E.aborted)break;if(e instanceof I){pe=!0,M(e.errorType)?d.logStatus(e.message):R(e)?w(`[bridge:work] Suppressed 403 error: ${e.message}`):(d.logError(e.message),v(e)),u("tengu_bridge_fatal_error",{status:e.status,error_type:e.errorType}),f(M(e.errorType)?"info":"error","bridge_fatal_error",{status:e.status,error_type:e.errorType});break}const s=A(e);if(isConnectionError(e)||isServerError(e)){const e=Date.now();null!==de&&e-de>pollSleepDetectionThresholdMs(p)&&(w(`[bridge:work] Detected system sleep (${Math.round((e-de)/1e3)}s gap), resetting error budget`),f("info","bridge_poll_sleep_detected",{gapMs:e-de}),ae=null,ie=0,le=null,ne=0),de=e,ae||(ae=e);const t=e-ae;if(t>=p.connGiveUpMs){d.logError(`Server unreachable for ${Math.round(t/6e4)} minutes, giving up.`),u("tengu_bridge_poll_give_up",{error_type:"connection",elapsed_ms:t}),f("error","bridge_poll_give_up",{error_type:"connection",elapsed_ms:t}),pe=!0;break}le=null,ne=0,ie=ie?Math.min(2*ie,p.connCapMs):p.connInitialMs;const o=addJitter(ie);d.logVerbose(`Connection error, retrying in ${formatDelay(o)} (${Math.round(t/1e3)}s elapsed): ${s}`),d.updateReconnectingStatus(formatDelay(o),x(t)),P().non_exclusive_heartbeat_interval_ms>0&&await heartbeatActiveWorkItems(),await S(o,E)}else{const e=Date.now();null!==de&&e-de>pollSleepDetectionThresholdMs(p)&&(w(`[bridge:work] Detected system sleep (${Math.round((e-de)/1e3)}s gap), resetting error budget`),f("info","bridge_poll_sleep_detected",{gapMs:e-de}),ae=null,ie=0,le=null,ne=0),de=e,le||(le=e);const t=e-le;if(t>=p.generalGiveUpMs){d.logError(`Persistent errors for ${Math.round(t/6e4)} minutes, giving up.`),u("tengu_bridge_poll_give_up",{error_type:"general",elapsed_ms:t}),f("error","bridge_poll_give_up",{error_type:"general",elapsed_ms:t}),pe=!0;break}ae=null,ie=0,ne=ne?Math.min(2*ne,p.generalCapMs):p.generalInitialMs;const o=addJitter(ne);d.logVerbose(`Poll failed, retrying in ${formatDelay(o)} (${Math.round(t/1e3)}s elapsed): ${s}`),d.updateReconnectingStatus(formatDelay(o),x(t)),P().non_exclusive_heartbeat_interval_ms>0&&await heartbeatActiveWorkItems(),await S(o,E)}}}stopStatusUpdates(),d.clearStatus();const ue=Date.now()-oe;u("tengu_bridge_shutdown",{active_sessions:T.size,loop_duration_ms:ue}),f("info","bridge_shutdown",{active_sessions:T.size,loop_duration_ms:ue});const ge=new Set(T.keys());g&&ge.add(g);const me=new Map(O);if(T.size>0){w(`[bridge:shutdown] Shutting down ${T.size} active session(s)`),d.logStatus(`Shutting down ${T.size} active session(s)…`);const e=new Map(W);for(const[e,s]of T.entries())w(`[bridge:shutdown] Sending SIGTERM to sessionId=${e}`),s.kill();const s=new AbortController;await Promise.race([Promise.allSettled([...T.values()].map(e=>e.done)),S(p.shutdownGraceMs??3e4,s.signal)]),s.abort();for(const[e,s]of T.entries())w(`[bridge:shutdown] Force-killing stuck sessionId=${e}`),s.forceKill();for(const e of Y.values())clearTimeout(e);if(Y.clear(),te?.cancelAll(),X.size>0){const e=[...X.values()];X.clear(),w(`[bridge:shutdown] Cleaning up ${e.length} worktree(s)`),await Promise.allSettled(e.map(e=>y(e.worktreePath,e.worktreeBranch,e.gitRoot,e.hookBased)))}await Promise.allSettled([...e.entries()].map(([e,s])=>i.stopWork(t,s,!0).catch(t=>d.logVerbose(`Failed to stop work ${s} for session ${e}: ${b(t)}`))))}if(re.size>0&&await Promise.allSettled([...re]),s("KAIROS")&&"single-session"===e.spawnMode&&g&&!pe)return d.logStatus("Resume this session by running `context remote-control --continue`"),void w(`[bridge:shutdown] Skipping archive+deregister to allow resume of session ${g}`);ge.size>0&&(w(`[bridge:shutdown] Archiving ${ge.size} session(s)`),await Promise.allSettled([...ge].map(e=>i.archiveSession(me.get(e)??j(e)).catch(s=>d.logVerbose(`Failed to archive session ${e}: ${b(s)}`)))));try{await i.deregisterEnvironment(t),w("[bridge:shutdown] Environment deregistered, bridge offline"),d.logVerbose("Environment deregistered.")}catch(e){d.logVerbose(`Failed to deregister environment: ${b(e)}`)}const{clearBridgePointer:we}=await import("./bridgePointer.js");await we(e.dir),d.logVerbose("Environment offline.")}const Y=new Set(["ECONNREFUSED","ECONNRESET","ETIMEDOUT","ENETUNREACH","EHOSTUNREACH"]);export function isConnectionError(e){return!!(e&&"object"==typeof e&&"code"in e&&"string"==typeof e.code&&Y.has(e.code))}export function isServerError(e){return!!e&&"object"==typeof e&&"code"in e&&"string"==typeof e.code&&"ERR_BAD_RESPONSE"===e.code}function addJitter(e){return Math.max(0,e+.25*e*(2*Math.random()-1))}function formatDelay(e){return e>=1e3?`${(e/1e3).toFixed(1)}s`:`${Math.round(e)}ms`}async function stopWorkWithRetry(e,s,t,o,r=1e3){for(let i=1;i<=3;i++)try{return await e.stopWork(s,t,!1),void w(`[bridge:work] stopWork succeeded for workId=${t} on attempt ${i}/3`)}catch(e){if(e instanceof I)return R(e)?w(`[bridge:work] Suppressed stopWork 403 for ${t}: ${e.message}`):o.logError(`Failed to stop work ${t}: ${e.message}`),void f("error","bridge_stop_work_failed",{attempts:i,fatal:!0});const s=b(e);if(i<3){const e=addJitter(r*Math.pow(2,i-1));o.logVerbose(`Failed to stop work ${t} (attempt ${i}/3), retrying in ${formatDelay(e)}: ${s}`),await S(e)}else o.logError(`Failed to stop work ${t} after 3 attempts: ${s}`),f("error","bridge_stop_work_failed",{attempts:3})}}function onSessionTimeout(e,s,t,o,r){w(`[bridge:session] sessionId=${e} timed out after ${x(s)}`),u("tengu_bridge_session_timeout",{timeout_ms:s}),t.logSessionFailed(e,`Session timed out after ${x(s)}`),o.add(e),r.kill()}const J=["session","same-dir","worktree"];function parseSpawnValue(e){return"session"===e?"single-session":"same-dir"===e?"same-dir":"worktree"===e?"worktree":`--spawn requires one of: ${J.join(", ")} (got: ${e??"<missing>"})`}function parseCapacityValue(e){const s=void 0===e?NaN:parseInt(e,10);return isNaN(s)||s<1?`--capacity requires a positive integer (got: ${e??"<missing>"})`:s}export function parseArgs(e){let t,o,r,i,n,l,d,c,p=!1,u=!1,g=!1,m=!1;for(let w=0;w<e.length;w++){const f=e[w];if("--help"===f||"-h"===f)g=!0;else if("--verbose"===f||"-v"===f)p=!0;else if("--sandbox"===f)u=!0;else if("--no-sandbox"===f)u=!1;else if("--debug-file"===f&&w+1<e.length)t=a(e[++w]);else if(f.startsWith("--debug-file="))t=a(f.slice(13));else if("--session-timeout"===f&&w+1<e.length)o=1e3*parseInt(e[++w],10);else if(f.startsWith("--session-timeout="))o=1e3*parseInt(f.slice(18),10);else if("--permission-mode"===f&&w+1<e.length)r=e[++w];else if(f.startsWith("--permission-mode="))r=f.slice(18);else if("--name"===f&&w+1<e.length)i=e[++w];else if(f.startsWith("--name="))i=f.slice(7);else if(s("KAIROS")&&"--session-id"===f&&w+1<e.length){if(c=e[++w],!c)return makeError("--session-id requires a value")}else if(s("KAIROS")&&f.startsWith("--session-id=")){if(c=f.slice(13),!c)return makeError("--session-id requires a value")}else if(!s("KAIROS")||"--continue"!==f&&"-c"!==f)if("--spawn"===f||f.startsWith("--spawn=")){if(void 0!==n)return makeError("--spawn may only be specified once");const s=parseSpawnValue(f.startsWith("--spawn=")?f.slice(8):e[++w]);if("single-session"!==s&&"same-dir"!==s&&"worktree"!==s)return makeError(s);n=s}else if("--capacity"===f||f.startsWith("--capacity=")){if(void 0!==l)return makeError("--capacity may only be specified once");const s=parseCapacityValue(f.startsWith("--capacity=")?f.slice(11):e[++w]);if("number"!=typeof s)return makeError(s);l=s}else if("--create-session-in-dir"===f)d=!0;else{if("--no-create-session-in-dir"!==f)return makeError(`Unknown argument: ${f}\nRun 'context remote-control --help' for usage.`);d=!1}else m=!0}return"single-session"===n&&void 0!==l?makeError("--capacity cannot be used with --spawn=session (single-session mode has fixed capacity 1)."):!c&&!m||void 0===n&&void 0===l&&void 0===d?c&&m?makeError("--session-id and --continue cannot be used together."):{verbose:p,sandbox:u,debugFile:t,sessionTimeoutMs:o,permissionMode:r,name:i,spawnMode:n,capacity:l,createSessionInDir:d,sessionId:c,continueSession:m,help:g}:makeError("--session-id and --continue cannot be used with --spawn, --capacity, or --create-session-in-dir.");function makeError(e){return{verbose:p,sandbox:u,debugFile:t,sessionTimeoutMs:o,permissionMode:r,name:i,spawnMode:n,capacity:l,createSessionInDir:d,sessionId:c,continueSession:m,help:g,error:e}}}const X=80;function deriveSessionTitle(e){const s=e.replace(/\s+/g," ").trim();return k(s,X)}async function fetchSessionTitle(e,s){const{getBridgeSession:t}=await import("./createSession.js"),o=await t(e,{baseUrl:s});return o?.title||void 0}export async function bridgeMain(r){const n=parseArgs(r);if(n.help)return void await async function(){const{EXTERNAL_PERMISSION_MODES:e}=await import("../types/permissions.js"),t=e.join(", "),o=await isMultiSessionSpawnEnabled(),r=o?" --spawn <mode> Spawn mode: same-dir, worktree, session\n (default: same-dir)\n --capacity <N> Max concurrent sessions in worktree or\n same-dir mode (default: 32)\n --[no-]create-session-in-dir Pre-create a session in the current\n directory; in worktree mode this session\n stays in cwd while on-demand sessions get\n isolated worktrees (default: on)\n":"",i=o?"\n Remote Control runs as a persistent server that accepts multiple concurrent\n sessions in the current directory. One session is pre-created on start so\n you have somewhere to type immediately. Use --spawn=worktree to isolate\n each on-demand session in its own git worktree, or --spawn=session for\n the classic single-session mode (exits when that session ends). Press 'w'\n during runtime to toggle between same-dir and worktree.\n":"",n=o?" - Worktree mode requires a git repository or WorktreeCreate/WorktreeRemove hooks\n":"",a=`\nRemote Control - Connect your local environment to claude.ai/code\n\nUSAGE\n context remote-control [options]\nOPTIONS\n --name <name> Name for the session (shown in claude.ai/code)\n${s("KAIROS")?" -c, --continue Resume the last session in this directory\n --session-id <id> Resume a specific session by ID (cannot be\n used with spawn flags or --continue)\n":""} --permission-mode <mode> Permission mode for spawned sessions\n (${t})\n --debug-file <path> Write debug logs to file\n -v, --verbose Enable verbose output\n -h, --help Show this help\n${r}\nDESCRIPTION\n Remote Control allows you to control sessions on your local device from\n claude.ai/code (https://claude.ai/code). Run this command in the\n directory you want to work in, then connect from the Claude app or web.\n${i}\nNOTES\n - Debes iniciar sesión con una cuenta Context que tenga suscripción\n - Run \`context\` first in the directory to accept the workspace trust dialog\n${n}`;console.log(a)}();n.error&&(console.error(`Error: ${n.error}`),process.exit(1));const{verbose:l,sandbox:p,debugFile:m,sessionTimeoutMs:_,permissionMode:h,name:k,spawnMode:$,capacity:y,createSessionInDir:M,sessionId:R,continueSession:x}=n;let D,A=R;const U=void 0!==$||void 0!==y||void 0!==M;if(void 0!==h){const{PERMISSION_MODES:e}=await import("../types/permissions.js"),s=e;s.includes(h)||(console.error(`Error: Invalid permission mode '${h}'. Valid modes: ${s.join(", ")}`),process.exit(1))}const j=a("."),{enableConfigs:F,checkHasTrustDialogAccepted:G}=await import("../utils/config.js");F();const{initSinks:V}=await import("../utils/sinks.js");V();const H=await isMultiSessionSpawnEnabled();U&&!H&&(await g("tengu_bridge_multi_session_denied",{used_spawn:void 0!==$,used_capacity:void 0!==y,used_create_session_in_dir:void 0!==M}),await Promise.race([Promise.all([c(),d()]),S(500,void 0,{unref:!0})]).catch(()=>{}),console.error("Error: Multi-session Remote Control is not enabled for your account yet."),process.exit(1));const{setOriginalCwd:L,setCwdState:z}=await import("../bootstrap/state.js");L(j),z(j),G()||(console.error(`Error: Workspace not trusted. Please run \`context\` in ${j} first to review and accept the workspace trust dialog.`),process.exit(1));const{clearOAuthTokenCache:q,checkAndRefreshOAuthTokenIfNeeded:K}=await import("../utils/auth.js"),{getBridgeAccessToken:Y,getBridgeBaseUrl:J}=await import("./bridgeConfig.js");Y()||(console.error(N),process.exit(1));const{getGlobalConfig:X,saveGlobalConfig:Q,getCurrentProjectConfig:Z,saveCurrentProjectConfig:ee}=await import("../utils/config.js");if(!X().remoteDialogSeen){const e=(await import("readline")).createInterface({input:process.stdin,output:process.stdout});console.log("\nRemote Control lets you access this CLI session from the web (claude.ai/code)\nor the Claude app, so you can pick up where you left off on any device.\n\nYou can disconnect remote access anytime by running /remote-control again.\n");const s=await new Promise(s=>{e.question("Enable Remote Control? (y/n) ",s)});e.close(),Q(e=>e.remoteDialogSeen?e:{...e,remoteDialogSeen:!0}),"y"!==s.toLowerCase()&&"yes"!==s.toLowerCase()&&process.exit(0)}if(s("KAIROS")&&x){const{readBridgePointerAcrossWorktrees:e}=await import("./bridgePointer.js"),s=await e(j);s||(console.error("Error: No recent session found in this directory or its worktrees. Run `context remote-control` to start a new one."),process.exit(1));const{pointer:t,dir:o}=s,r=Math.round(t.ageMs/6e4),i=r<60?`${r}m`:`${Math.round(r/60)}h`,n=o!==j?` from worktree ${o}`:"";console.error(`Resuming session ${t.sessionId} (${i} ago)${n}…`),A=t.sessionId,D=o}const se=J();!se.startsWith("http://")||se.includes("localhost")||se.includes("127.0.0.1")||(console.error("Error: Remote Control base URL uses HTTP. Only HTTPS or localhost HTTP is allowed."),process.exit(1));const te="ant"===process.env.USER_TYPE?process.env.CONTEXT_BRIDGE_SESSION_INGRESS_URL??process.env.CLAUDE_BRIDGE_SESSION_INGRESS_URL??se:se,{getBranch:oe,getRemoteUrl:re,findGitRoot:ie}=await import("../utils/git.js"),{hasWorktreeCreateHook:ne}=await import("../utils/hooks.js"),ae=ne()||null!==ie(j);let le,de,ce=H?Z().remoteControlSpawnMode:void 0;if("worktree"!==ce||ae||(console.error("Warning: Saved spawn mode is worktree but this directory is not a git repository. Falling back to same-dir."),ce=void 0,ee(e=>void 0===e.remoteControlSpawnMode?e:{...e,remoteControlSpawnMode:void 0})),H&&!ce&&ae&&void 0===$&&!A&&process.stdin.isTTY){const e=(await import("readline")).createInterface({input:process.stdin,output:process.stdout});console.log("\nClaude Remote Control is launching in spawn mode which lets you create new sessions in this project from Context Code on Web or your Mobile app. Learn more here: https://code.claude.com/docs/en/remote-control\n\nSpawn mode for this project:\n [1] same-dir — sessions share the current directory (default)\n [2] worktree — each session gets an isolated git worktree\n\nThis can be changed later or explicitly set with --spawn=same-dir or --spawn=worktree.\n");const s=await new Promise(s=>{e.question("Choose [1/2] (default: 1): ",s)});e.close();const t="2"===s.trim()?"worktree":"same-dir";ce=t,u("tengu_bridge_spawn_mode_chosen",{spawn_mode:t}),ee(e=>e.remoteControlSpawnMode===t?e:{...e,remoteControlSpawnMode:t})}A?(de="single-session",le="resume"):void 0!==$?(de=$,le="flag"):void 0!==ce?(de=ce,le="saved"):(de=H?"same-dir":"single-session",le="gate_default");const pe="single-session"===de?1:y??32,ue=M??!0;if(!A){const{clearBridgePointer:e}=await import("./bridgePointer.js");await e(j)}"worktree"!==de||ae||(console.error("Error: Worktree mode requires a git repository or WorktreeCreate hooks configured. Use --spawn=session for single-session mode."),process.exit(1));const ge=await oe(),me=await re(),we=o(),fe=t(),{handleOAuth401Error:_e}=await import("../utils/auth.js"),he=E({baseUrl:se,getAccessToken:Y,runnerVersion:e.VERSION,onDebug:w,onAuth401:_e,getTrustedDeviceToken:O});let be;if(s("KAIROS")&&A){try{C(A,"sessionId")}catch{console.error(`Error: Invalid session ID "${A}". Session IDs must not contain unsafe characters.`),process.exit(1)}await K(),q();const{getBridgeSession:e}=await import("./createSession.js"),s=await e(A,{baseUrl:se,getAccessToken:Y});if(!s){if(D){const{clearBridgePointer:e}=await import("./bridgePointer.js");await e(D)}console.error(`Error: Session ${A} not found. It may have been archived or expired, or your login may have lapsed (run \`context /login\`).`),process.exit(1)}if(!s.environment_id){if(D){const{clearBridgePointer:e}=await import("./bridgePointer.js");await e(D)}console.error(`Error: Session ${A} has no environment_id. It may never have been attached to a bridge.`),process.exit(1)}be=s.environment_id,w(`[bridge:init] Resuming session ${A} on environment ${be}`)}const ke={dir:j,machineName:we,branch:ge,gitRepoUrl:me,maxSessions:pe,spawnMode:de,verbose:l,sandbox:p,bridgeId:fe,workerType:"claude_code",environmentId:t(),reuseEnvironmentId:be,apiBaseUrl:se,sessionIngressUrl:te,debugFile:m,sessionTimeoutMs:_};let ve,Se,$e;w(`[bridge:init] bridgeId=${fe}${be?` reuseEnvironmentId=${be}`:""} dir=${j} branch=${ge} gitRepoUrl=${me} machine=${we}`),w(`[bridge:init] apiBaseUrl=${se} sessionIngressUrl=${te}`),w(`[bridge:init] sandbox=${p}${m?` debugFile=${m}`:""}`);try{const e=await he.registerBridgeEnvironment(ke);ve=e.environment_id,Se=e.environment_secret}catch(e){u("tengu_bridge_registration_failed",{status:e instanceof I?e.status:void 0}),console.error(e instanceof I&&404===e.status?"Remote Control environments are not available for your account.":`Error: ${b(e)}`),process.exit(1)}if(s("KAIROS")&&A)if(be&&ve!==be)v(new Error(`Bridge resume env mismatch: requested ${be}, backend returned ${ve}. Falling back to fresh session.`)),console.warn(`Warning: Could not resume session ${A} — its environment has expired. Creating a fresh session instead.`);else{const e=B(A),s=e===A?[A]:[A,e];let t,o=!1;for(const e of s)try{await he.reconnectSession(ve,e),w(`[bridge:init] Session ${e} re-queued via bridge/reconnect`),$e=A,o=!0;break}catch(s){t=s,w(`[bridge:init] reconnectSession(${e}) failed: ${b(s)}`)}if(!o){const e=t,s=e instanceof I;if(D&&s){const{clearBridgePointer:e}=await import("./bridgePointer.js");await e(D)}console.error(s?`Error: ${b(e)}`:`Error: Failed to reconnect session ${A}: ${b(e)}\nThe session may still be resumable — try running the same command again.`),process.exit(1)}}w(`[bridge:init] Registered, server environmentId=${ve}`);const ye=P();u("tengu_bridge_started",{max_sessions:ke.maxSessions,has_debug_file:!!ke.debugFile,sandbox:ke.sandbox,verbose:ke.verbose,heartbeat_interval_ms:ye.non_exclusive_heartbeat_interval_ms,spawn_mode:ke.spawnMode,spawn_mode_source:le,multi_session_gate:H,pre_create_session:ue,worktree_available:ae}),f("info","bridge_started",{max_sessions:ke.maxSessions,sandbox:ke.sandbox,spawn_mode:ke.spawnMode});const Ie=W({execPath:process.execPath,scriptArgs:spawnScriptArgs(),env:process.env,verbose:l,sandbox:p,debugFile:m,permissionMode:h,onDebug:w,onActivity:(e,s)=>{w(`[bridge:activity] sessionId=${e} ${s.type} ${s.summary}`)},onPermissionRequest:(e,s,t)=>{w(`[bridge:perm] sessionId=${e} tool=${s.request.tool_name} request_id=${s.request_id} (not auto-approving)`)}}),Ee=T({verbose:l}),{parseGitHubRepository:Me}=await import("../utils/detectRepository.js"),Re=me?Me(me):null,Ce=Re?Re.split("/").pop():i(j);Ee.setRepoInfo(Ce,ge);const xe="single-session"!==de&&ae;xe&&Ee.setSpawnModeDisplay(de);const onStdinData=e=>{if(3!==e[0]&&4!==e[0])if(32!==e[0]){if(119===e[0]){if(!xe)return;const e="same-dir"===ke.spawnMode?"worktree":"same-dir";return ke.spawnMode=e,u("tengu_bridge_spawn_mode_toggled",{spawn_mode:e}),Ee.logStatus("worktree"===e?"Spawn mode: worktree (new sessions get isolated git worktrees)":"Spawn mode: same-dir (new sessions share the current directory)"),Ee.setSpawnModeDisplay(e),Ee.refreshDisplay(),void ee(s=>s.remoteControlSpawnMode===e?s:{...s,remoteControlSpawnMode:e})}}else Ee.toggleQr();else process.emit("SIGINT")};process.stdin.isTTY&&(process.stdin.setRawMode(!0),process.stdin.resume(),process.stdin.on("data",onStdinData));const Te=new AbortController,onSigint=()=>{w("[bridge:shutdown] SIGINT received, shutting down"),Te.abort()},onSigterm=()=>{w("[bridge:shutdown] SIGTERM received, shutting down"),Te.abort()};process.on("SIGINT",onSigint),process.on("SIGTERM",onSigterm);let De=s("KAIROS")&&$e?$e:null;if(ue&&(!s("KAIROS")||!$e)){const{createBridgeSession:e}=await import("./createSession.js");try{De=await e({environmentId:ve,title:k,events:[],gitRepoUrl:me,branch:ge,signal:Te.signal,baseUrl:se,getAccessToken:Y,permissionMode:h}),De&&w(`[bridge:init] Created initial session ${De}`)}catch(e){w(`[bridge:init] Session creation failed (non-fatal): ${b(e)}`)}}let Ae=null;if(De&&"single-session"===de){const{writeBridgePointer:e}=await import("./bridgePointer.js"),s={sessionId:De,environmentId:ve,source:"standalone"};await e(ke.dir,s),Ae=setInterval(e,36e5,ke.dir,s),Ae.unref?.()}try{await runBridgeLoop(ke,ve,Se,he,Ie,Ee,Te.signal,void 0,De??void 0,async()=>(q(),await K(),Y()))}finally{null!==Ae&&clearInterval(Ae),process.off("SIGINT",onSigint),process.off("SIGTERM",onSigterm),process.stdin.off("data",onStdinData),process.stdin.isTTY&&process.stdin.setRawMode(!1),process.stdin.pause()}process.exit(0)}export class BridgeHeadlessPermanentError extends Error{constructor(e){super(e),this.name="BridgeHeadlessPermanentError"}}export async function runBridgeHeadless(s,r){const{dir:i,log:n}=s;process.chdir(i);const{setOriginalCwd:a,setCwdState:l}=await import("../bootstrap/state.js");a(i),l(i);const{enableConfigs:d,checkHasTrustDialogAccepted:c}=await import("../utils/config.js");d();const{initSinks:p}=await import("../utils/sinks.js");if(p(),!c())throw new BridgeHeadlessPermanentError(`Workspace not trusted: ${i}. Run \`context\` in that directory first to accept the trust dialog.`);if(!s.getAccessToken())throw new Error(N);const{getBridgeBaseUrl:u}=await import("./bridgeConfig.js"),g=u();if(g.startsWith("http://")&&!g.includes("localhost")&&!g.includes("127.0.0.1"))throw new BridgeHeadlessPermanentError("Remote Control base URL uses HTTP. Only HTTPS or localhost HTTP is allowed.");const m="ant"===process.env.USER_TYPE?process.env.CONTEXT_BRIDGE_SESSION_INGRESS_URL??process.env.CLAUDE_BRIDGE_SESSION_INGRESS_URL??g:g,{getBranch:w,getRemoteUrl:f,findGitRoot:_}=await import("../utils/git.js"),{hasWorktreeCreateHook:h}=await import("../utils/hooks.js");if("worktree"===s.spawnMode){if(!(h()||null!==_(i)))throw new BridgeHeadlessPermanentError(`Worktree mode requires a git repository or WorktreeCreate hooks. Directory ${i} has neither.`)}const k=await w(),v=await f(),S=o(),$=t(),y={dir:i,machineName:S,branch:k,gitRepoUrl:v,maxSessions:s.capacity,spawnMode:s.spawnMode,verbose:!1,sandbox:s.sandbox,bridgeId:$,workerType:"claude_code",environmentId:t(),apiBaseUrl:g,sessionIngressUrl:m,sessionTimeoutMs:s.sessionTimeoutMs},I=E({baseUrl:g,getAccessToken:s.getAccessToken,runnerVersion:e.VERSION,onDebug:n,onAuth401:s.onAuth401,getTrustedDeviceToken:O});let M,R;try{const e=await I.registerBridgeEnvironment(y);M=e.environment_id,R=e.environment_secret}catch(e){throw new Error(`Bridge registration failed: ${b(e)}`)}const C=W({execPath:process.execPath,scriptArgs:spawnScriptArgs(),env:process.env,verbose:!1,sandbox:s.sandbox,permissionMode:s.permissionMode,onDebug:n}),x=function(e){const noop=()=>{};return{printBanner:(s,t)=>e(`registered environmentId=${t} dir=${s.dir} spawnMode=${s.spawnMode} capacity=${s.maxSessions}`),logSessionStart:(s,t)=>e(`session start ${s}`),logSessionComplete:(s,t)=>e(`session complete ${s} (${t}ms)`),logSessionFailed:(s,t)=>e(`session failed ${s}: ${t}`),logStatus:e,logVerbose:e,logError:s=>e(`error: ${s}`),logReconnected:s=>e(`reconnected after ${s}ms`),addSession:(s,t)=>e(`session attached ${s}`),removeSession:s=>e(`session detached ${s}`),updateIdleStatus:noop,updateReconnectingStatus:noop,updateSessionStatus:noop,updateSessionActivity:noop,updateSessionCount:noop,updateFailedStatus:noop,setSpawnModeDisplay:noop,setRepoInfo:noop,setDebugLogPath:noop,setAttached:noop,setSessionTitle:noop,clearStatus:noop,toggleQr:noop,refreshDisplay:noop}}(n);let T;if(x.printBanner(y,M),s.createSessionOnStart){const{createBridgeSession:e}=await import("./createSession.js");try{const t=await e({environmentId:M,title:s.name,events:[],gitRepoUrl:v,branch:k,signal:r,baseUrl:g,getAccessToken:s.getAccessToken,permissionMode:s.permissionMode});t&&(T=t,n(`created initial session ${t}`))}catch(e){n(`session pre-creation failed (non-fatal): ${b(e)}`)}}await runBridgeLoop(y,M,R,I,C,x,r,void 0,T,async()=>s.getAccessToken())}
|
package/dist/src/bridge/types.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const DEFAULT_SESSION_TIMEOUT_MS=864e5;export const BRIDGE_LOGIN_INSTRUCTION="Remote Control
|
|
1
|
+
export const DEFAULT_SESSION_TIMEOUT_MS=864e5;export const BRIDGE_LOGIN_INSTRUCTION="Remote Control solo está disponible con suscripciones de Context. Usa `/login` para iniciar sesión con tu cuenta de Context.";export const BRIDGE_LOGIN_ERROR="Error: debes iniciar sesión para usar Remote Control.\n\n"+BRIDGE_LOGIN_INSTRUCTION;export const REMOTE_CONTROL_DISCONNECTED_MSG="Remote Control desconectado.";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{clearAuthRelatedCaches as e,performLogout as o}from"../../commands/logout/logout.js";import{setMainLoopModelOverride as i}from"../../bootstrap/state.js";import{getSecureStorage as t}from"../../utils/secureStorage/index.js";import{logEvent as r}from"../../services/analytics/index.js";import{getSSLErrorHint as s}from"../../services/api/errorUtils.js";import{fetchAndStoreClaudeCodeFirstTokenDate as a}from"../../services/api/firstTokenDate.js";import{createAndStoreApiKey as n,fetchAndStoreUserRoles as c,refreshOAuthToken as l,shouldUseClaudeAIAuth as u,storeOAuthAccountInfo as p}from"../../services/oauth/client.js";import{getOauthProfileFromOauthToken as d}from"../../services/oauth/getOauthProfile.js";import{OAuthService as m}from"../../services/oauth/index.js";import{startGeminiCliOAuthFlow as g}from"../../services/oauth/geminiCli.js";import{clearOAuthTokenCache as f,getAnthropicApiKeyWithSource as h,getAuthTokenSource as v,getClaudeAIOAuthTokens as w,getOpenAIOAuthTokens as _,getOauthAccountInfo as A,removeApiKey as x,saveOpenAIOAuthTokens as O,saveGeminiGoogleOAuthTokens as
|
|
1
|
+
import{clearAuthRelatedCaches as e,performLogout as o}from"../../commands/logout/logout.js";import{setMainLoopModelOverride as i}from"../../bootstrap/state.js";import{getSecureStorage as t}from"../../utils/secureStorage/index.js";import{logEvent as r}from"../../services/analytics/index.js";import{getSSLErrorHint as s}from"../../services/api/errorUtils.js";import{fetchAndStoreClaudeCodeFirstTokenDate as a}from"../../services/api/firstTokenDate.js";import{createAndStoreApiKey as n,fetchAndStoreUserRoles as c,refreshOAuthToken as l,shouldUseClaudeAIAuth as u,storeOAuthAccountInfo as p}from"../../services/oauth/client.js";import{getOauthProfileFromOauthToken as d}from"../../services/oauth/getOauthProfile.js";import{OAuthService as m}from"../../services/oauth/index.js";import{startGeminiCliOAuthFlow as g}from"../../services/oauth/geminiCli.js";import{clearOAuthTokenCache as f,getAnthropicApiKeyWithSource as h,getAuthTokenSource as v,getClaudeAIOAuthTokens as w,getOpenAIOAuthTokens as _,getOauthAccountInfo as A,removeApiKey as x,saveOpenAIOAuthTokens as O,saveGeminiGoogleOAuthTokens as E,saveProviderApiKey as y,getSubscriptionType as C,isUsing3PServices as j,saveOAuthTokensIfNeeded as T,validateForceLoginOrg as P,removeProviderScopedCredentials as U,removeProviderApiKey as b}from"../../utils/auth.js";import{saveGlobalConfig as k}from"../../utils/config.js";import{logForDebugging as S}from"../../utils/debug.js";import{isRunningOnHomespace as $}from"../../utils/envUtils.js";import{errorMessage as I}from"../../utils/errors.js";import{logError as H}from"../../utils/log.js";import{getAPIProvider as L}from"../../utils/model/providers.js";import{setStoredActiveProviderPreference as D}from"../../utils/model/providerProfilesDb.js";import{isProfiledProvider as N,setActiveProfileForProvider as R}from"../../utils/model/providerProfiles.js";import{getLastUsedProviderProfile as z,removeProviderProfile as K}from"../../utils/model/providerProfiles.js";import{clearProviderBaseUrl as W,setProviderBaseUrl as B}from"../../utils/model/providerBaseUrls.js";import{getInitialSettings as F}from"../../utils/settings/settings.js";import{jsonStringify as M}from"../../utils/slowOperations.js";import{buildAccountProperties as X,buildAPIProviderProperties as Y}from"../../utils/status.js";export async function installOAuthTokens(o,s="anthropic",l){if(l||await async function(e){const{flushTelemetry:o}=await import("../../utils/telemetry/instrumentation.js");if(await o(),"openai"===e)return void _.cache?.clear?.();await x();const i=t(),r=i.read();if(r){const e=Object.fromEntries(Object.entries(r.providerProfileOauth??{}).filter(([e])=>!e.startsWith("claude/"))),{claudeAiOauth:o,...t}=r,s={...t,providerProfileOauth:e};Object.keys(s).length>0?i.update(s):i.delete()}w.cache?.clear?.(),k(e=>({...e,oauthAccount:void 0}))}(s),"anthropic"===s){const e=o.profile??await d(o.accessToken);e?p({accountUuid:e.account.uuid,emailAddress:e.account.email,organizationUuid:e.organization.uuid,displayName:e.account.display_name||void 0,hasExtraUsageEnabled:e.organization.has_extra_usage_enabled??void 0,billingType:e.organization.billing_type??void 0,subscriptionCreatedAt:e.organization.subscription_created_at??void 0,accountCreatedAt:e.account.created_at}):o.tokenAccount&&p({accountUuid:o.tokenAccount.uuid,emailAddress:o.tokenAccount.emailAddress,organizationUuid:o.tokenAccount.organizationUuid})}const m="openai"===s?O(o,l):T(o,l);if(f(),m.warning&&r("tengu_oauth_storage_warning",{warning:m.warning}),"anthropic"===s&&await c(o.accessToken).catch(e=>S(String(e),{level:"error"})),"anthropic"===s&&u(o.scopes))await a().catch(e=>S(String(e),{level:"error"}));else if("anthropic"===s){if(!await n(o.accessToken))throw new Error("No se pudo crear la API key. El servidor aceptó la solicitud pero no devolvió una clave.")}D("openai"===s?"openai":"claude"),i(void 0),await e()}export async function authLogin({email:e,sso:o,console:i,claudeai:t,provider:a}){i&&t&&(process.stderr.write("Error: --console and --claudeai cannot be used together.\n"),process.exit(1));const n=F(),c=n.forceLoginMethod?"claudeai"===n.forceLoginMethod:!i,p=n.forceLoginOrgUUID,d=a?.trim().toLowerCase(),f=process.env.CONTEXT_CODE_OAUTH_REFRESH_TOKEN??process.env.CLAUDE_CODE_OAUTH_REFRESH_TOKEN;if(f){const e=process.env.CONTEXT_CODE_OAUTH_SCOPES??process.env.CLAUDE_CODE_OAUTH_SCOPES;e||(process.stderr.write('CONTEXT_CODE_OAUTH_SCOPES (legacy: CLAUDE_CODE_OAUTH_SCOPES) is required when using CONTEXT_CODE_OAUTH_REFRESH_TOKEN.\nSet it to the space-separated scopes the refresh token was issued with\n(e.g. "user:inference" or "user:profile user:inference user:sessions:claude_code user:mcp_servers").\n'),process.exit(1));const o=e.split(/\s+/).filter(Boolean);try{r("tengu_login_from_refresh_token",{});const e=await l(f,{scopes:o});await installOAuthTokens(e);const i=await P();i.valid||(process.stderr.write(i.message+"\n"),process.exit(1)),k(e=>e.hasCompletedOnboarding?e:{...e,hasCompletedOnboarding:!0}),r("tengu_oauth_success",{loginWithClaudeAi:u(e.scopes)}),process.stdout.write("Inicio de sesión exitoso.\n"),process.exit(0)}catch(e){H(e);const o=s(e);process.stderr.write(`Error al iniciar sesión: ${I(e)}\n${o?o+"\n":""}`),process.exit(1)}}const h=o?"sso":void 0,v=new m;try{if(r("tengu_oauth_flow_start",{loginWithClaudeAi:c}),"gemini-google"===d){const e=await g();E(e),D("gemini-google"),process.stdout.write("Inicio de sesión exitoso.\n"),process.exit(0)}const o="openai"===d?"openai":"anthropic",i="openai"!==o&&c,t=await v.startOAuthFlow(async e=>{process.stdout.write("Abriendo el navegador para iniciar sesión…\n"),process.stdout.write(`Si el navegador no se abre, visita: ${e}\n`)},{loginWithClaudeAi:i,provider:o,loginHint:e,loginMethod:h,orgUUID:p});if(await installOAuthTokens(t,o),"anthropic"===o){const e=await P();e.valid||(process.stderr.write(e.message+"\n"),process.exit(1))}r("tengu_oauth_success",{loginWithClaudeAi:i}),process.stdout.write("Inicio de sesión exitoso.\n"),process.exit(0)}catch(e){H(e);const o=s(e);process.stderr.write(`Error al iniciar sesión: ${I(e)}\n${o?o+"\n":""}`),process.exit(1)}finally{v.cleanup()}}export async function authStatus(e){const{source:o,hasToken:i}=v(),{source:t}=h(),r=!!process.env.ANTHROPIC_API_KEY&&!$(),s=L(),a=A(),n=C(),c=j(),l=i||"none"!==t||r||c;let u="none";if("openai"===s?u="openai":c?u="third_party":"claude.ai"===o?u="claude.ai":"apiKeyHelper"===o?u="api_key_helper":"none"!==o?u="oauth_token":"ANTHROPIC_API_KEY"===t||r?u="api_key":"/login managed key"===t&&(u="claude.ai"),e.text){const e=[...X(),...Y()];let o=!1;for(const i of e){const e="string"==typeof i.value?i.value:Array.isArray(i.value)?i.value.join(", "):null;null!==e&&"none"!==e&&(o=!0,i.label?process.stdout.write(`${i.label}: ${e}\n`):process.stdout.write(`${e}\n`))}!o&&r&&process.stdout.write("API key: ANTHROPIC_API_KEY\n"),l||process.stdout.write("openai"===s?"Sesión no iniciada. Ejecuta context auth login para autenticarte con OpenAI / Codex.\n":"Sesión no iniciada. Ejecuta context auth login para autenticarte.\n")}else{const e="none"!==t?t:r?"ANTHROPIC_API_KEY":null,o={loggedIn:l,authMethod:u,apiProvider:s};e&&(o.apiKeySource=e),"claude.ai"===u&&(o.email=a?.emailAddress??null,o.orgId=a?.organizationUuid??null,o.orgName=a?.organizationName??null,o.subscriptionType=n??null),process.stdout.write(M(o,null,2)+"\n")}process.exit(l?0:1)}export async function authLogout(){try{await o({clearOnboarding:!1})}catch{process.stderr.write("Error al cerrar sesión.\n"),process.exit(1)}process.stdout.write(`Sesión cerrada exitosamente de tu cuenta ${function(){const e=L();return"openai"===e?"OpenAI / Codex":"bedrock"===e?"AWS Bedrock":"vertex"===e?"Google Vertex AI":"foundry"===e?"Microsoft Foundry":"Anthropic"}()}.\n`),process.exit(0)}export async function authProviderApiKey(e){const o=e.provider.trim().toLowerCase(),i=["openrouter","ollama-cloud","gemini-api","zai","minimax","nvidia"];i.includes(o)||(process.stderr.write(`Provider no soportado para API key: ${o}. Soportados: ${i.join(", ")}\n`),process.exit(1));const t=e.profile?.trim();t&&N(o)&&R(o,t),await y(o,e.apiKey),D(o),process.stdout.write(`API key guardada para ${o}${t?` (perfil ${t})`:""}.\n`),process.exit(0)}export async function authProviderBaseUrl(e){const o=e.provider.trim().toLowerCase(),i=["ollama","ollama-cloud","openrouter","gemini-google","zai","minimax","nvidia"];i.includes(o)||(process.stderr.write(`Provider no soportado para base URL: ${o}. Soportados: ${i.join(", ")}\n`),process.exit(1));const t=e.profile?.trim();t&&N(o)&&R(o,t);const r=B(o,e.baseUrl);D(o),process.stdout.write(`Base URL guardada para ${o}: ${r}${t?` (perfil ${t})`:""}.\n`),process.exit(0)}export async function authSetProviderPreference(e){const o=e.provider.trim().toLowerCase();D(o),process.stdout.write(`Provider activo establecido a ${o}.\n`),process.exit(0)}export async function authProviderRemove(e){const o=e.provider.trim().toLowerCase();o||(process.stderr.write("Debes indicar un provider.\n"),process.exit(1));const i=o,t=e.profile?.trim();let r=null;if(N(i)){const e=t||z(i)?.name;if(e){const o=K(i,e);o&&(await U(o.id),r=`${o.provider}/${o.name}`)}}["openrouter","ollama-cloud","gemini-api","zai","minimax","nvidia"].includes(o)&&await b(o);["ollama","ollama-cloud","openrouter","gemini-google","zai","minimax","nvidia"].includes(o)&&W(o),process.stdout.write(r?`Proveedor eliminado: ${r}.\n`:`Credenciales/base URL limpiadas para ${o}.\n`),process.exit(0)}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{jsx as e}from"react/jsx-runtime";import{stat as o}from"fs/promises";import t from"p-map";import{cwd as s}from"process";import{MCPServerDesktopImportDialog as r}from"../../components/MCPServerDesktopImportDialog.js";import{render as c}from"../../ink.js";import{KeybindingSetup as n}from"../../keybindings/KeybindingProviderSetup.js";import{logEvent as i}from"../../services/analytics/index.js";import{clearMcpClientConfig as a,clearServerTokensFromLocalStorage as p,getMcpClientConfig as l,readClientSecret as m,saveMcpClientSecret as u}from"../../services/mcp/auth.js";import{connectToServer as d,getMcpServerConnectionBatchSize as f}from"../../services/mcp/client.js";import{addMcpConfig as h,getAllMcpConfigs as g,getMcpConfigByName as y,getMcpConfigsByScope as v,removeMcpConfig as $}from"../../services/mcp/config.js";import{describeMcpConfigFilePath as j,ensureConfigScope as w,getScopeLabel as b}from"../../services/mcp/utils.js";import{AppStateProvider as _}from"../../state/AppState.js";import{getCurrentProjectConfig as C,getGlobalConfig as S,saveCurrentProjectConfig as k}from"../../utils/config.js";import{isFsInaccessible as P}from"../../utils/errors.js";import{gracefulShutdown as
|
|
1
|
+
import{jsx as e}from"react/jsx-runtime";import{stat as o}from"fs/promises";import t from"p-map";import{cwd as s}from"process";import{MCPServerDesktopImportDialog as r}from"../../components/MCPServerDesktopImportDialog.js";import{render as c}from"../../ink.js";import{KeybindingSetup as n}from"../../keybindings/KeybindingProviderSetup.js";import{logEvent as i}from"../../services/analytics/index.js";import{clearMcpClientConfig as a,clearServerTokensFromLocalStorage as p,getMcpClientConfig as l,readClientSecret as m,saveMcpClientSecret as u}from"../../services/mcp/auth.js";import{connectToServer as d,getMcpServerConnectionBatchSize as f}from"../../services/mcp/client.js";import{addMcpConfig as h,getAllMcpConfigs as g,getMcpConfigByName as y,getMcpConfigsByScope as v,removeMcpConfig as $}from"../../services/mcp/config.js";import{describeMcpConfigFilePath as j,ensureConfigScope as w,getScopeLabel as b}from"../../services/mcp/utils.js";import{AppStateProvider as _}from"../../state/AppState.js";import{getCurrentProjectConfig as C,getGlobalConfig as S,saveCurrentProjectConfig as k}from"../../utils/config.js";import{isFsInaccessible as P}from"../../utils/errors.js";import{gracefulShutdown as x}from"../../utils/gracefulShutdown.js";import{safeParseJSON as M}from"../../utils/json.js";import{getPlatform as A}from"../../utils/platform.js";import{cliError as H,cliOk as O}from"../exit.js";async function checkMcpServerHealth(e,o){try{const t=await d(e,o);return"connected"===t.type?"✓ Connected":"needs-auth"===t.type?"! Needs authentication":"✗ Failed to connect"}catch(e){return"✗ Connection error"}}export async function mcpServeHandler({debug:e,verbose:t}){const r=s();i("tengu_mcp_start",{});try{await o(r)}catch(e){throw P(e)&&H(`Error: Directory ${r} does not exist`),e}try{const{setup:o}=await import("../../setup.js");await o(r,"default",!1,!1,void 0,!1);const{startMCPServer:s}=await import("../../entrypoints/mcp.js");await s(r,e??!1,t??!1)}catch(e){H(`Error: Failed to start MCP server: ${e}`)}}export async function mcpRemoveHandler(e,o){const t=y(e),cleanupSecureStorage=()=>{!t||"sse"!==t.type&&"http"!==t.type||(p(e,t),a(e,t))};try{if(o.scope){const t=w(o.scope);i("tengu_mcp_delete",{name:e,scope:t}),await $(e,t),cleanupSecureStorage(),process.stdout.write(`Removed MCP server ${e} from ${t} config\n`),O(`File modified: ${j(t)}`)}const t=C(),s=S(),{servers:r}=v("project"),c=!!r[e],n=[];if(t.mcpServers?.[e]&&n.push("local"),c&&n.push("project"),s.mcpServers?.[e]&&n.push("user"),0===n.length)H(`No MCP server found with name: "${e}"`);else if(1===n.length){const o=n[0];i("tengu_mcp_delete",{name:e,scope:o}),await $(e,o),cleanupSecureStorage(),process.stdout.write(`Removed MCP server "${e}" from ${o} config\n`),O(`File modified: ${j(o)}`)}else process.stderr.write(`MCP server "${e}" exists in multiple scopes:\n`),n.forEach(e=>{process.stderr.write(` - ${b(e)} (${j(e)})\n`)}),process.stderr.write("\nTo remove from a specific scope, use:\n"),n.forEach(o=>{process.stderr.write(` claude mcp remove "${e}" -s ${o}\n`)}),H()}catch(e){H(e.message)}}export async function mcpListHandler(){i("tengu_mcp_list",{});const{servers:e}=await g();if(0===Object.keys(e).length)console.log("No hay servidores MCP configurados. Usa `context mcp add` para agregar uno.");else{console.log("Checking MCP server health...\n");const o=Object.entries(e),s=await t(o,async([e,o])=>({name:e,server:o,status:await checkMcpServerHealth(e,o)}),{concurrency:f()});for(const{name:e,server:o,status:t}of s)if("sse"===o.type)console.log(`${e}: ${o.url} (SSE) - ${t}`);else if("http"===o.type)console.log(`${e}: ${o.url} (HTTP) - ${t}`);else if("claudeai-proxy"===o.type)console.log(`${e}: ${o.url} - ${t}`);else if(!o.type||"stdio"===o.type){const s=Array.isArray(o.args)?o.args:[];console.log(`${e}: ${o.command} ${s.join(" ")} - ${t}`)}}await x(0)}export async function mcpGetHandler(e){i("tengu_mcp_get",{name:e});const o=y(e);o||H(`No MCP server found with name: ${e}`),console.log(`${e}:`),console.log(` Scope: ${b(o.scope)}`);const t=await checkMcpServerHealth(e,o);if(console.log(` Status: ${t}`),"sse"===o.type){if(console.log(" Type: sse"),console.log(` URL: ${o.url}`),o.headers){console.log(" Headers:");for(const[e,t]of Object.entries(o.headers))console.log(` ${e}: ${t}`)}if(o.oauth?.clientId||o.oauth?.callbackPort){const t=[];if(o.oauth.clientId){t.push("client_id configured");const s=l(e,o);s?.clientSecret&&t.push("client_secret configured")}o.oauth.callbackPort&&t.push(`callback_port ${o.oauth.callbackPort}`),console.log(` OAuth: ${t.join(", ")}`)}}else if("http"===o.type){if(console.log(" Type: http"),console.log(` URL: ${o.url}`),o.headers){console.log(" Headers:");for(const[e,t]of Object.entries(o.headers))console.log(` ${e}: ${t}`)}if(o.oauth?.clientId||o.oauth?.callbackPort){const t=[];if(o.oauth.clientId){t.push("client_id configured");const s=l(e,o);s?.clientSecret&&t.push("client_secret configured")}o.oauth.callbackPort&&t.push(`callback_port ${o.oauth.callbackPort}`),console.log(` OAuth: ${t.join(", ")}`)}}else if("stdio"===o.type){console.log(" Type: stdio"),console.log(` Command: ${o.command}`);const e=Array.isArray(o.args)?o.args:[];if(console.log(` Args: ${e.join(" ")}`),o.env){console.log(" Environment:");for(const[e,t]of Object.entries(o.env))console.log(` ${e}=${t}`)}}console.log(`\nTo remove this server, run: claude mcp remove "${e}" -s ${o.scope}`),await x(0)}export async function mcpAddJsonHandler(e,o,t){try{const s=w(t.scope),r=M(o),c=t.clientSecret&&r&&"object"==typeof r&&"type"in r&&("sse"===r.type||"http"===r.type)&&"url"in r&&"string"==typeof r.url&&"oauth"in r&&r.oauth&&"object"==typeof r.oauth&&"clientId"in r.oauth?await m():void 0;await h(e,r,s);const n=r&&"object"==typeof r&&"type"in r?String(r.type||"stdio"):"stdio";c&&r&&"object"==typeof r&&"type"in r&&("sse"===r.type||"http"===r.type)&&"url"in r&&"string"==typeof r.url&&u(e,{type:r.type,url:r.url},c),i("tengu_mcp_add",{scope:s,source:"json",type:n}),O(`Added ${n} MCP server ${e} to ${s} config`)}catch(e){H(e.message)}}export async function mcpAddFromDesktopHandler(o){try{const t=w(o.scope),s=A();i("tengu_mcp_add",{scope:t,platform:s,source:"desktop"});const{readClaudeDesktopMcpServers:a}=await import("../../utils/claudeDesktop.js"),p=await a();0===Object.keys(p).length&&O("No se encontraron servidores MCP en la configuración de Context Desktop, o el archivo de configuración no existe.");const{unmount:l}=await c(e(_,{children:e(n,{children:e(r,{servers:p,scope:t,onDone:()=>{l()}})})}),{exitOnCtrlC:!0})}catch(e){H(e.message)}}export async function mcpResetChoicesHandler(){i("tengu_mcp_reset_mcpjson_choices",{}),k(e=>({...e,enabledMcpjsonServers:[],disabledMcpjsonServers:[],enableAllProjectMcpServers:!1})),O("All project-scoped (.mcp.json) server approvals and rejections have been reset.\nYou will be prompted for approval next time you start Context Code.")}
|