@quantiya/codevibe-claude-plugin 1.0.39 → 1.0.41

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codevibe-claude",
3
- "version": "1.0.39",
3
+ "version": "1.0.41",
4
4
  "description": "Sync Claude Code sessions with iOS mobile app via AWS backend. Control Claude Code from your phone with real-time bidirectional synchronization.",
5
5
  "author": {
6
6
  "name": "CodeVibe Team"
package/dist/server.js CHANGED
@@ -1,18 +1,18 @@
1
- "use strict";var Ie=Object.create;var B=Object.defineProperty;var ke=Object.getOwnPropertyDescriptor;var Pe=Object.getOwnPropertyNames;var be=Object.getPrototypeOf,Ae=Object.prototype.hasOwnProperty;var xe=(f,e)=>{for(var t in e)B(f,t,{get:e[t],enumerable:!0})},ae=(f,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of Pe(e))!Ae.call(f,n)&&n!==t&&B(f,n,{get:()=>e[n],enumerable:!(i=ke(e,n))||i.enumerable});return f};var T=(f,e,t)=>(t=f!=null?Ie(be(f)):{},ae(e||!f||!f.__esModule?B(t,"default",{value:f,enumerable:!0}):t,f)),Te=f=>ae(B({},"__esModule",{value:!0}),f);var Re={};xe(Re,{McpServer:()=>J,parseInteractivePromptInput:()=>Ee});module.exports=Te(Re);var _=T(require("fs")),O=T(require("path")),Y=T(require("os")),ye=require("child_process"),ve=require("util"),we=require("child_process"),V=require("crypto");var pe=T(require("os")),ce=T(require("path")),de=require("@quantiya/codevibe-core"),r=(0,de.createLogger)({name:"codevibe-claude",logFile:ce.default.join(pe.default.tmpdir(),"codevibe-claude-mcp.log"),level:"info"});var p=require("@quantiya/codevibe-core");var ee=T(require("express")),D=T(require("fs")),te=T(require("path")),ne=T(require("os")),le=require("@quantiya/codevibe-core");var y=require("@quantiya/codevibe-core");var H=class{constructor(){this.assignedPort=0;this.app=(0,ee.default)(),this.setupMiddleware(),this.setupRoutes()}setSessionId(e){this.sessionId=e}getPort(){return this.assignedPort}setupMiddleware(){this.app.use(ee.default.json({limit:"1mb"})),this.app.use((e,t,i)=>{r.debug(`${e.method} ${e.path}`,{body:e.body,query:e.query}),i()}),this.app.use((e,t,i,n)=>{r.error("Express error:",e);let s={success:!1,error:e.message||"Internal server error"};i.status(500).json(s)})}setupRoutes(){this.app.get("/health",this.handleHealth.bind(this)),this.app.post("/event",this.handleEvent.bind(this)),process.env.NODE_ENV!=="production"&&this.app.post("/test/execute",this.handleTestExecute.bind(this))}handleHealth(e,t){let i={success:!0,data:{status:"healthy",uptime:process.uptime(),version:"0.1.0",timestamp:new Date().toISOString()}};t.json(i)}async handleEvent(e,t){try{let i=e.body;if(!i.session_id){let o={success:!1,error:"Missing required field: session_id"};t.status(400).json(o);return}if(!i.hook_event_name){let o={success:!1,error:"Missing required field: hook_event_name"};t.status(400).json(o);return}let n=this.transformHookToEvent(i);r.info("Received event from hook",{sessionId:i.session_id,hookEvent:i.hook_event_name,type:n.type}),this.eventHandler?await this.eventHandler(n):r.warn("No event handler registered");let s={success:!0,message:"Event processed successfully"};t.json(s)}catch(i){r.error("Error handling event:",i);let n={success:!1,error:i instanceof Error?i.message:"Unknown error"};t.status(500).json(n)}}async handleTestExecute(e,t){try{let{sessionId:i,prompt:n}=e.body;if(!i||!n){let o={success:!1,error:"Missing required fields: sessionId, prompt"};t.status(400).json(o);return}r.info("Test execute request",{sessionId:i,prompt:n});let s={success:!0,message:"Test execution endpoint - not implemented yet",data:{sessionId:i,prompt:n}};t.json(s)}catch(i){r.error("Error in test execute:",i);let n={success:!1,error:i instanceof Error?i.message:"Unknown error"};t.status(500).json(n)}}transformHookToEvent(e){let t,i,n={cwd:e.cwd,hook_event_name:e.hook_event_name,...e.metadata||{}};if(e.type&&e.content!==void 0)t=e.type,i=e.content;else switch(e.hook_event_name){case"SessionStart":t=y.EventType.NOTIFICATION,i="Session started",n.source=e.source;break;case"SessionEnd":t=y.EventType.NOTIFICATION,i=`Session ended: ${e.reason||"unknown"}`,n.reason=e.reason;break;case"UserPromptSubmit":t=y.EventType.USER_PROMPT,i=e.prompt||"";break;case"PostToolUse":t=y.EventType.TOOL_USE,i=JSON.stringify({tool_name:e.tool_name,tool_input:e.tool_input,tool_response:e.tool_response}),n.tool_name=e.tool_name;break;case"Notification":t=y.EventType.NOTIFICATION,i=e.message||"",n.notification_type=e.notification_type;break;default:t=y.EventType.NOTIFICATION,i=`Hook event: ${e.hook_event_name}`}return{session_id:e.session_id,hook_event_name:e.hook_event_name,type:t,source:y.EventSource.DESKTOP,content:i,metadata:n}}onEvent(e){this.eventHandler=e}async start(e){let t=e||this.sessionId;return t&&(this.sessionId=t),new Promise((i,n)=>{try{let s=(0,le.getConfig)(),o=s.server.dynamicPort?0:s.server.port;this.server=this.app.listen(o,s.server.host,()=>{let a=this.server.address();this.assignedPort=a.port,r.info(`HTTP API listening on http://${s.server.host}:${this.assignedPort}`),this.sessionId&&this.writePortFile(this.sessionId,this.assignedPort),i(this.assignedPort)}),this.server.on("error",a=>{r.error("HTTP server error:",a),n(a)})}catch(s){n(s)}})}writePortFile(e,t){let i=te.join(ne.tmpdir(),`codevibe-claude-${e}.port`);try{D.writeFileSync(i,t.toString()),r.info(`Port file written: ${i} -> ${t}`)}catch(n){r.error(`Failed to write port file: ${i}`,n)}}removePortFile(){if(this.sessionId){let e=te.join(ne.tmpdir(),`codevibe-claude-${this.sessionId}.port`);try{D.existsSync(e)&&(D.unlinkSync(e),r.info(`Port file removed: ${e}`))}catch(t){r.warn(`Failed to remove port file: ${e}`,t)}}}async stop(e){return new Promise((t,i)=>{this.sessionId&&e?.protectedSessionIds?.has(this.sessionId)?r.info("Skipping port file removal \u2014 another daemon still serves this session",{sessionId:this.sessionId}):this.removePortFile(),this.server?this.server.close(n=>{n?(r.error("Error stopping HTTP server:",n),i(n)):(r.info("HTTP API stopped"),t())}):t()})}};var ue=require("child_process"),me=require("@quantiya/codevibe-core");var X=class{async executePrompt(e,t){let i=(0,me.getConfig)(),n=i.claude.defaultTimeout;return r.info("Executing prompt from mobile",{sessionId:e,promptLength:t.length,timeout:n}),new Promise(s=>{let o=["--resume",e,"--print","--output-format","stream-json",t];r.debug("Spawning Claude command",{command:i.claude.command,args:o});let a=(0,ue.spawn)(i.claude.command,o,{stdio:["pipe","pipe","pipe"],shell:!0}),l="",u="",d=!1,g=setTimeout(()=>{d=!0,r.warn("Command execution timed out",{sessionId:e,timeout:n}),a.kill("SIGTERM")},n);a.stdout?.on("data",c=>{let m=c.toString();l+=m,r.debug("Command stdout",{output:m.slice(0,200)})}),a.stderr?.on("data",c=>{let m=c.toString();u+=m,r.debug("Command stderr",{output:m.slice(0,200)})}),a.on("close",c=>{clearTimeout(g);let m={success:c===0&&!d,output:l,error:u,exitCode:c||void 0,timedOut:d};m.success?r.info("Command executed successfully",{sessionId:e,exitCode:c,outputLength:l.length}):r.error("Command execution failed",{sessionId:e,exitCode:c,timedOut:d,error:u.slice(0,500)}),s(m)}),a.on("error",c=>{clearTimeout(g),r.error("Failed to spawn command",{error:c.message}),s({success:!1,error:c.message,timedOut:!1})})})}detectInteractivePrompt(e){return[/\[Y\/n\]/i,/\[y\/N\]/i,/\(y\/n\)/i,/Continue\?/i,/Proceed\?/i].some(i=>i.test(e))}extractPromptText(e){let t=e.split(`
2
- `);for(let i=t.length-1;i>=0;i--){let n=t[i].trim();if(this.detectInteractivePrompt(n))return n}return null}};var ge=require("child_process"),fe=require("util");var ie=(0,fe.promisify)(ge.exec),z=class{async answerInteractivePrompt(e,t,i={}){let{pressEnter:n=!0}=i;r.info("Attempting to answer interactive prompt",{sessionId:e,response:t,pressEnter:n});try{let s=process.env.CODEVIBE_TMUX_SESSION;return r.info("Checking tmux session environment",{tmuxSession:s||"(not set)",allEnvKeys:Object.keys(process.env).filter(o=>o.includes("CODEVIBE")||o.includes("TMUX"))}),s?(r.info("Using tmux send-keys",{tmuxSession:s,pressEnter:n}),await this.sendViaTmux(s,t,n),r.info("Successfully sent response to interactive prompt",{sessionId:e,response:t,pressEnter:n}),!0):(r.error("No tmux session found - codevibe-claude wrapper is required",{sessionId:e,hint:"Start Claude Code using the codevibe-claude wrapper script"}),!1)}catch(s){return r.error("Failed to answer interactive prompt",{sessionId:e,error:s instanceof Error?s.message:String(s)}),!1}}async sendViaTmux(e,t,i){let n=t.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\$/g,"\\$").replace(/`/g,"\\`");r.info("Sending via tmux",{sessionName:e,inputLength:t.length,pressEnter:i});try{let s=`tmux send-keys -t "${e}" -l "${n}"`,o=await ie(s);if(r.info("tmux send-keys (text) completed",{stdout:o.stdout||"(empty)",stderr:o.stderr||"(empty)"}),i){await this.delay(500);let a=`tmux send-keys -t "${e}" Enter`,l=await ie(a);r.info("tmux send-keys (Enter) completed",{stdout:l.stdout||"(empty)",stderr:l.stderr||"(empty)"})}else r.info("tmux send-keys: skipping Enter (caller requested digit-only)")}catch(s){throw r.error("tmux send-keys failed",{sessionName:e,error:s}),s}}async sendKey(e,t){let i=process.env.CODEVIBE_TMUX_SESSION;if(!i)return r.error("No tmux session found for sendKey",{sessionId:e,keyName:t}),!1;try{let n=`tmux send-keys -t "${i}" ${t}`,s=await ie(n);return r.info("tmux send-keys (single key) completed",{sessionId:e,keyName:t,stdout:s.stdout||"(empty)",stderr:s.stderr||"(empty)"}),!0}catch(n){return r.error("tmux send-keys (single key) failed",{sessionId:e,keyName:t,error:n instanceof Error?n.message:String(n)}),!1}}delay(e){return new Promise(t=>setTimeout(t,e))}isPromptResponse(e){let t=e.trim().toLowerCase();return!!(t==="y"||t==="n"||t==="yes"||t==="no"||/^[0-9]+$/.test(t)||/^[a-z]$/.test(t)||["exit","quit","q","continue","skip","abort","retry","cancel"].includes(t))}};var Ce=(0,ve.promisify)(we.exec),_e="/exit",he="CODEVIBE_TMUX_SESSION";async function Fe(f,e){let t=async(i,n)=>{try{await Ce(i)}catch(s){r.warn("tmux send-keys failed during self-terminate",{sessionName:f,label:n,error:String(s)})}};await t(`tmux send-keys -t "${f}" C-c`,"ctrl-c"),await new Promise(i=>setTimeout(i,200)),await t(`tmux send-keys -t "${f}" -l "${e}"`,"quit-text"),await new Promise(i=>setTimeout(i,500)),await t(`tmux send-keys -t "${f}" Enter`,"enter")}var J=class f{constructor(e){this.activeSessions=new Map;this.assignedPort=0;this.sessionKey=null;this.claudeToBackendSessionId=new Map;this.pendingMobilePrompts=new Map;this.nextPromptGen=1;this.httpApi=new H,this.commandExecutor=new X,this.promptResponder=new z,this.initialSessionId=e}static{this.MOBILE_PROMPT_EXPIRY_MS=3e3}getPort(){return this.assignedPort}generateBackendSessionId(e){return`claude-${e}`}trackMobilePrompt(e,t){this.pendingMobilePrompts.has(e)||this.pendingMobilePrompts.set(e,[]),this.pendingMobilePrompts.get(e).push({prompt:t.trim(),timestamp:Date.now()}),r.debug("Tracking mobile prompt for deduplication",{sessionId:e,promptLength:t.length})}isRecentMobilePrompt(e,t){let i=this.pendingMobilePrompts.get(e);if(!i)return!1;let n=Date.now(),s=t.trim(),o=[],a=!1;for(let l of i)if(!(n-l.timestamp>f.MOBILE_PROMPT_EXPIRY_MS)){if(!a&&l.prompt===s){a=!0,r.debug("Found matching mobile prompt, filtering duplicate",{sessionId:e});continue}o.push(l)}return o.length>0?this.pendingMobilePrompts.set(e,o):this.pendingMobilePrompts.delete(e),a}writePortFile(e){let t=O.join(Y.tmpdir(),`codevibe-claude-${e}.port`);try{_.writeFileSync(t,this.assignedPort.toString()),r.info(`Port file written: ${t} -> ${this.assignedPort}`)}catch(i){r.error(`Failed to write port file: ${t}`,i)}}removePortFile(e){let t=O.join(Y.tmpdir(),`codevibe-claude-${e}.port`);try{_.existsSync(t)&&(_.unlinkSync(t),r.info(`Port file removed: ${t}`))}catch(i){r.warn(`Failed to remove port file: ${t}`,i)}}hasOtherLiveDaemonForSession(e){try{let t=(0,ye.execSync)("ps -eww -o pid= -o args=",{encoding:"utf8",timeout:2e3}),i=process.pid;for(let n of t.split(`
3
- `)){let s=n.trim();if(!s)continue;let o=s.indexOf(" ");if(o<0)continue;let a=parseInt(s.substring(0,o),10);if(isNaN(a)||a===i)continue;let l=s.substring(o+1);if(/node.*codevibe-claude.*server\.js/.test(l)&&l.includes(e))return!0}return!1}catch(t){return r.warn('hasOtherLiveDaemonForSession: ps query failed; falling back to "no other daemon"',{error:String(t)}),!1}}async start(){try{if(r.info("Starting CodeVibe MCP Server...",{environment:(0,p.getEnvironment)()}),this.appSyncClient=new p.AppSyncClient,await this.appSyncClient.authenticateWithStoredTokens()){r.info("Authenticated with stored OAuth tokens",{userId:this.appSyncClient.getCurrentUserId(),email:this.appSyncClient.getCurrentUserEmail()}),await(0,p.registerDeviceEncryptionKey)(this.appSyncClient,r),(0,p.startDeviceKeyWatcher)(this.appSyncClient,r);try{let t=await this.appSyncClient.sweepOrphanSessions({agentType:"CLAUDE"});t>0&&r.info("Orphan sweep: marked stale Claude sessions INACTIVE",{swept:t})}catch(t){r.warn("Orphan sweep failed, continuing startup",{error:t instanceof Error?t.message:String(t)})}}else r.error('Authentication failed. Run "codevibe-claude login" first.'),console.error('Not authenticated. Run "codevibe-claude login" to sign in.'),process.exit(1);this.httpApi.onEvent(this.handleEventFromHook.bind(this)),this.assignedPort=await this.httpApi.start(this.initialSessionId),r.info("MCP Server started successfully",{port:this.assignedPort,host:(0,p.getConfig)().server.host,dynamicPort:(0,p.getConfig)().server.dynamicPort,sessionId:this.initialSessionId,authenticated:this.appSyncClient.isAuthenticated(),userId:this.appSyncClient.getCurrentUserId()})}catch(e){throw r.error("Failed to start MCP Server:",e),e}}async stop(){r.info("Stopping MCP Server...");let e=Array.from(this.activeSessions.keys()),t=new Set;r.info(`Marking ${e.length} active session(s) as INACTIVE...`);for(let i of e){let n=this.activeSessions.get(i);n?.mobileEndWatcher&&(n.mobileEndWatcher.stop(),n.mobileEndWatcher=void 0)}for(let i of e)try{let n=this.activeSessions.get(i);if(n&&this.hasOtherLiveDaemonForSession(n.claudeSessionId)){r.info("Another daemon serves this session \u2014 skipping mark INACTIVE AND port file removal during shutdown",{sessionId:i,claudeSessionId:n.claudeSessionId,myPid:process.pid}),t.add(n.claudeSessionId);continue}await this.appSyncClient.updateSession({sessionId:i,status:p.SessionStatus.INACTIVE}),r.info("Session marked as INACTIVE during shutdown",{sessionId:i}),n&&this.removePortFile(n.claudeSessionId)}catch(n){r.warn("Failed to mark session as INACTIVE during shutdown",{sessionId:i,error:n})}this.appSyncClient.cleanupSubscriptions(),this.activeSessions.clear(),await this.httpApi.stop({protectedSessionIds:t}),r.info("MCP Server stopped")}async handleEventFromHook(e){let{session_id:t,hook_event_name:i,type:n,content:s}=e;r.info("Processing hook event",{sessionId:t,hookEvent:i,type:n});try{i==="SessionStart"?await this.handleSessionStart(e):i==="SessionEnd"&&await this.handleSessionEnd(e);let o=this.claudeToBackendSessionId.get(t)||this.generateBackendSessionId(t);if(i==="UserPromptSubmit"){let g=this.activeSessions.get(o);if(g?.completedAskUserQuestionFingerprints?.size){let c=g.completedAskUserQuestionFingerprints.size;g.completedAskUserQuestionFingerprints.clear(),r.info("Turn boundary \u2014 cleared closed-AskUserQuestion fingerprints",{sessionId:o,clearedCount:c})}}if(n===p.EventType.USER_PROMPT&&e.source===p.EventSource.DESKTOP&&i==="UserPromptSubmit"&&s&&this.isRecentMobilePrompt(o,s)){r.info("Skipping duplicate USER_PROMPT from mobile-originated prompt",{sessionId:o,contentLength:s.length});return}if(n===p.EventType.INTERACTIVE_PROMPT){(typeof e.prompt_id!="string"||e.prompt_id.length===0)&&(e.prompt_id=`synth-${(0,V.randomUUID)()}`,r.info("Synthesized prompt_id for INTERACTIVE_PROMPT (hook omitted it)",{sessionId:o,synthesizedPromptId:e.prompt_id}));let g=this.activeSessions.get(o),c;if(g&&e.metadata?.tool_name==="AskUserQuestion"&&(c=this.computeAskUserQuestionFingerprint(e.metadata.tool_input?.questions),c)){let m=g.activeAskUserQuestionFingerprint===c,F=g.completedAskUserQuestionFingerprints?.has(c)??!1;if(m||F){r.info("Dropping duplicate INTERACTIVE_PROMPT \u2014 AskUserQuestion already tracked",{sessionId:o,fingerprint:c.slice(0,16),status:m?"in-flight":"completed",hookEvent:e.hook_event_name,promptId:e.prompt_id});return}}if(g){this.clearPromptState(g),g.waitingForPromptResponse=!0,g.pendingPromptId=e.prompt_id;let m=this.nextPromptGen++;g.promptGenerationToken={promptId:e.prompt_id||"",gen:m},c&&(g.activeAskUserQuestionFingerprint=c),r.info("Interactive prompt detected - will parse options from tmux",{sessionId:o,promptId:e.prompt_id,tokenGen:m,askUserQuestionFingerprint:c?.slice(0,16)})}this.sendInteractivePromptAsync(o,e,s).catch(m=>{r.error("Failed to send interactive prompt with dynamic options",{error:m})});return}let a=s,l=e.metadata,u=!1;r.info("Hook event encryption state",{type:n,sessionId:o,hasSessionKey:!!this.sessionKey,sessionKeyLength:this.sessionKey?.length||0}),this.sessionKey?(a=p.cryptoService.encryptContent(s,this.sessionKey),l&&(l={encrypted:p.cryptoService.encryptMetadata(l,this.sessionKey)}),u=!0,r.info("Event encrypted for hook",{type:n,sessionId:o,isEncrypted:!0})):r.warn("No session key - event will NOT be encrypted",{type:n,sessionId:o});let d=await this.appSyncClient.createEvent({sessionId:o,type:n,source:e.source,content:a,metadata:l,promptId:e.prompt_id,isEncrypted:u?!0:void 0});if(n===p.EventType.USER_PROMPT&&e.source===p.EventSource.DESKTOP){let g=this.activeSessions.get(o);g?.waitingForPromptResponse&&(this.promoteFingerprintAndClearPromptState(g),r.info("Clearing prompt wait state - new desktop prompt received",{sessionId:o}))}r.debug("Event sent to AppSync successfully")}catch(o){throw r.error("Failed to process hook event:",o),o}}async handleSessionStart(e){let t=e.session_id,i=this.generateBackendSessionId(t),n=e.metadata?.cwd||process.cwd();this.claudeToBackendSessionId.set(t,i),r.info("Session started",{claudeSessionId:t,sessionId:i,cwd:n});let s=Array.from(this.activeSessions.keys()).filter(u=>u!==i);if(s.length>0){r.info(`Marking ${s.length} previous session(s) as INACTIVE`);for(let u of s){let d=this.activeSessions.get(u);d?.mobileEndWatcher&&(d.mobileEndWatcher.stop(),d.mobileEndWatcher=void 0);try{await this.appSyncClient.updateSession({sessionId:u,status:p.SessionStatus.INACTIVE}),r.info("Previous session marked INACTIVE",{prevId:u,newSessionId:i})}catch(g){r.warn("Failed to mark previous session as INACTIVE",{prevId:u,error:g})}d&&this.removePortFile(d.claudeSessionId),this.activeSessions.delete(u)}}this.writePortFile(t);let o=this.appSyncClient.getCurrentUserId(),a={sessionId:i,claudeSessionId:t,userId:o,projectPath:n,cwd:n,createdAt:new Date,subscriptionActive:!1,waitingForPromptResponse:!1,metadata:e.metadata||{}};this.activeSessions.set(i,a);try{let u=await(0,p.resumeOrCreateSession)({sessionId:i,userId:a.userId,agentType:p.AgentType.CLAUDE,projectPath:n,metadata:e.metadata||{}},this.appSyncClient,r);if(this.sessionKey=u.sessionKey,u.resumed&&!u.sessionKey){let d=await p.keychainManager.getDeviceId();r.error("Device key not found in session encryptedKeys",{sessionId:i,pluginDeviceId:d}),console.error(`
4
- \u26A0\uFE0F E2E ENCRYPTION WARNING: Cannot decrypt this session!`),console.error(` Your device ID (${d.substring(0,8)}...) is not in session's encryption keys.`),console.error(" This happens if your device key was regenerated after the session was created."),console.error(` SOLUTION: Start a new Claude Code session instead of resuming this one.
5
- `)}}catch(u){if(this.isSessionLimitExceeded(u)){this.displaySubscriptionLimitError(u,"session"),this.activeSessions.delete(i),this.removePortFile(t);return}r.error("Failed to create/resume session:",u)}this.subscribeToMobileEvents(i),this.appSyncClient.startHeartbeat(i);let l=this.activeSessions.get(i);l&&(l.mobileEndWatcher=this.appSyncClient.watchForMobileEnd(i,async()=>{r.info("Mobile ended session \u2014 sending desktop quit",{sessionId:i});let u=process.env[he];if(!u){r.warn("No tmux session set; skipping desktop self-terminate",{sessionId:i,expectedEnv:he});return}await Fe(u,_e)}))}async handleSessionEnd(e){let t=e.session_id,i=this.claudeToBackendSessionId.get(t)||this.generateBackendSessionId(t);r.info("Session ended",{claudeSessionId:t,sessionId:i,reason:e.metadata?.reason});let n=this.activeSessions.get(i);if(n?.mobileEndWatcher&&(n.mobileEndWatcher.stop(),n.mobileEndWatcher=void 0),this.removePortFile(t),n?.waitingForPromptResponse&&(r.info("Clearing prompt wait state - session ending",{sessionId:i}),this.clearPromptState(n)),this.appSyncClient.stopHeartbeat(i),n)try{await this.appSyncClient.updateSession({sessionId:i,status:p.SessionStatus.INACTIVE}),r.info("Session marked as INACTIVE in AppSync",{sessionId:i})}catch(s){r.warn("Failed to update session in AppSync:",s)}else r.warn("Cannot update session - session state not found",{sessionId:i});this.activeSessions.delete(i),this.claudeToBackendSessionId.delete(t),r.debug("Session cleanup completed",{sessionId:i})}subscribeToMobileEvents(e){r.info("Subscribing to mobile events",{sessionId:e});let t=this.activeSessions.get(e);if(!t){r.error("Session not found",{sessionId:e});return}this.appSyncClient.subscribeToEvents(e,async i=>{await this.dispatchMobileEvent(e,i)},i=>{r.error("Subscription error",{sessionId:e,error:i})}),t.subscriptionActive=!0,r.info("Subscription active",{sessionId:e})}async dispatchMobileEvent(e,t){r.info("Received mobile event",{eventId:t.eventId,type:t.type,sessionId:t.sessionId,isEncrypted:t.isEncrypted});let i,n,s=!1,o,a,l;if(t.type===p.EventType.USER_PROMPT||t.type===p.EventType.PROMPT_RESPONSE)if(n=this.activeSessions.get(e),!n)i="no-session";else if(n.processedEventIds?.has(t.eventId))i="skip-dedup";else if(n.inFlightEventIds?.has(t.eventId))i="drop-event-redeliver";else if(n.waitingForPromptResponse){let c=n.promptGenerationToken;if(!c)i="regular";else if(t.type===p.EventType.USER_PROMPT&&n.hasReceivedPromptResponse&&(!t.promptId||t.promptId.length===0))i="drop-stale-answer";else if(t.promptId&&t.promptId.length>0&&c.promptId.length>0&&t.promptId!==c.promptId)i="drop-stale-answer";else{let m=c.promptId.length>0?c.promptId:`__prompt_gen_${c.gen}`;n.inFlightPromptIds?.has(m)?i="drop-in-flight":(n.inFlightPromptIds||(n.inFlightPromptIds=new Set),n.inFlightEventIds||(n.inFlightEventIds=new Set),n.inFlightPromptIds.add(m),n.inFlightEventIds.add(t.eventId),s=!0,a=m,l=t.eventId,o={promptId:c.promptId,gen:c.gen},i="walker")}}else i="regular";else i="not-user-prompt";let d=t.content||"";if(t.isEncrypted&&this.sessionKey)try{d=p.cryptoService.decryptContent(t.content,this.sessionKey),r.debug("Event decrypted successfully",{eventId:t.eventId})}catch(c){r.error("Failed to decrypt event:",{eventId:t.eventId,error:c}),d=t.content}let g={...t,content:d};if(i!=="skip-dedup")try{await this.appSyncClient.updateEventStatus({eventId:t.eventId,sessionId:t.sessionId,timestamp:t.timestamp,deliveryStatus:p.DeliveryStatus.DELIVERED}),r.info("Event marked as DELIVERED",{eventId:t.eventId})}catch(c){r.warn("Failed to mark event as DELIVERED",{eventId:t.eventId,error:c})}if(i==="skip-dedup"){r.info("[walker] Subscription-level dedup hit (already processed) \u2014 skipping",{sessionId:e,eventId:t.eventId});return}if(i==="drop-stale-answer"){r.info("[walker] Stale answer dropped \u2014 event.promptId does not match current pending promptId",{sessionId:e,eventId:t.eventId,eventPromptId:t.promptId,currentPromptId:n?.promptGenerationToken?.promptId}),n&&(n.processedEventIds||(n.processedEventIds=new Set),n.processedEventIds.add(t.eventId));try{await this.markEventExecuted(t)}catch(c){r.warn("[walker] markEventExecuted threw on stale-answer drop \u2014 relying on processedEventIds Set",{sessionId:e,eventId:t.eventId,error:String(c)})}return}if(i==="drop-in-flight"){r.warn("[walker] Subscription-level in-flight guard \u2014 dropping duplicate USER_PROMPT (different eventId, same prompt)",{sessionId:e,eventId:t.eventId}),n&&(n.processedEventIds||(n.processedEventIds=new Set),n.processedEventIds.add(t.eventId));try{await this.markEventExecuted(t)}catch(c){r.warn("[walker] markEventExecuted threw on subscription-level duplicate drop \u2014 relying on processedEventIds Set",{sessionId:e,eventId:t.eventId,error:String(c)})}return}if(i==="drop-event-redeliver"){r.info("[walker] Subscription-level event-level redelivery \u2014 silent skip (original still in flight)",{sessionId:e,eventId:t.eventId});return}if(i==="walker"){t.type===p.EventType.PROMPT_RESPONSE&&n&&(n.hasReceivedPromptResponse=!0),await this.handleMobilePromptResponse(e,t,d,n,s,o,a,l);return}if(i==="regular"){if(t.type===p.EventType.PROMPT_RESPONSE){r.warn("Received PROMPT_RESPONSE with no active walker \u2014 dropping",{sessionId:e,eventId:t.eventId,promptId:t.promptId}),n&&(n.processedEventIds||(n.processedEventIds=new Set),n.processedEventIds.add(t.eventId));try{await this.markEventExecuted(t)}catch(c){r.warn("markEventExecuted threw on PROMPT_RESPONSE orphan drop \u2014 relying on processedEventIds Set",{sessionId:e,eventId:t.eventId,error:String(c)})}return}await this.executeMobilePrompt(e,g);return}if(i==="no-session"){r.warn("Received mobile prompt input for unknown session \u2014 ignoring",{sessionId:e,eventId:t.eventId,type:t.type});return}}async handleMobilePromptResponse(e,t,i,n,s=!1,o,a,l){let u=o??n.promptGenerationToken,d=a,g=l;if(!s&&u){let c=u.promptId.length>0?u.promptId:`__prompt_gen_${u.gen}`;if(n.inFlightPromptIds?.has(c)){r.warn("[walker] Duplicate mobile USER_PROMPT for same prompt \u2014 dropping",{sessionId:e,eventId:t.eventId,lockKey:c}),await this.markEventExecutedIdempotent(n,t);return}n.inFlightPromptIds||(n.inFlightPromptIds=new Set),n.inFlightEventIds||(n.inFlightEventIds=new Set),n.inFlightPromptIds.add(c),n.inFlightEventIds.add(t.eventId),d=c,g=t.eventId}try{if(!s&&n.processedEventIds?.has(t.eventId)){r.info("[walker] Redelivered event already processed \u2014 skipping",{sessionId:e,eventId:t.eventId});return}let c=i.trim(),m=n.pendingPromptId,M=n.pendingSubmitMap,L=M?Object.keys(M).length:3,S=this.parseInteractivePromptInput(c,L);r.info("Parsed interactive prompt input",{sessionId:e,content:c,parsed:S,hasSubmitMap:!!M});let P=()=>{let v=n.promptGenerationToken,b=v?.gen,I=u?.gen;return b!==I?(r.warn("[walker] Token mismatch \u2014 external cleanup or new prompt during in-flight handler \u2014 aborting",{sessionId:e,eventId:t.eventId,entryToken:u,currentToken:v}),!0):!1};if(P()){await this.markEventExecutedIdempotent(n,t);return}{let v=n.pendingQuestionsQueue!==void 0,I=c.trim().match(/^(\d+)$/);if(v&&n.pendingCurrentQuestion&&I){let w=n.pendingCurrentQuestion.options?.length??0,h=I[1],E=parseInt(h,10),k=!Number.isFinite(E)||E<1||E>w,A=String(E)!==h;if(k||A){if(r.info("V2 walker \u2014 bare out-of-range or non-canonical option; routing to cancel",{sessionId:e,option:h,optionNum:E,realOptionCount:w,isOutOfRange:k,isNonCanonical:A,isSubmitStep:!!n.pendingCurrentQuestion._isSubmit,parsedAction:S.action}),await this.markEventExecutedIdempotent(n,t),P())return;await this.cancelV2WalkerAndExit(e,n,"invalid_option",!1,P);return}}}if(S.action==="select_option"){let v=M?.[S.option]||S.option,b=n.pendingQuestionsQueue!==void 0;r.info("User selected option",{option:S.option,terminalInput:v,isV2AskUserQuestion:b});let I=await this.promptResponder.answerInteractivePrompt(e,v,{pressEnter:!0});if(P()){await this.markEventExecutedIdempotent(n,t);return}if(I){if(await this.markEventExecutedIdempotent(n,t),P())return;if(!m){r.warn("emitAnswerAck called without promptId \u2014 clearing state + skipping ack",{sessionId:e,source:"select_option",eventId:t.eventId}),this.promoteFingerprintAndClearPromptState(n);return}let w=(n.pendingQuestionsQueue?.length??0)===0;try{if(b){let x=parseInt(S.option,10)-1,U=n.pendingCurrentQuestion?.options?.[x],Q=typeof U=="string"?U:U&&typeof U=="object"?U.label:`option ${S.option}`,se=n.pendingCurrentQuestion?._isSubmit===!0,oe=Q.toLowerCase(),W;se&&oe==="cancel"?W="\u2192 Cancel \u2014 AskUserQuestion cancelled, no answers submitted":se&&oe.startsWith("submit")?W="\u2192 Submit answers \u2014 AskUserQuestion completed":W=`\u2192 ${Q}`,await this.emitUserChoice(e,W)}else await this.emitAnswerAck(e,`Selected option ${S.option}`,{promptId:m,questionIndex:0,isTerminal:w})}catch(x){r.warn("[walker] user-choice/ack emit failed \u2014 continuing to STEP 7/8",{sessionId:e,promptId:m,isV2AskUserQuestion:b,error:x instanceof Error?x.message:String(x)})}if(P())return;let h=n.pendingQuestionsQueue?.shift();if(h&&(n.pendingCurrentQuestion=h),!h){n.activeAskUserQuestionFingerprint&&(n.completedAskUserQuestionFingerprints||(n.completedAskUserQuestionFingerprints=new Set),n.completedAskUserQuestionFingerprints.add(n.activeAskUserQuestionFingerprint),r.info("AskUserQuestion V2 walker complete \u2014 fingerprint marked closed",{sessionId:e,fingerprint:n.activeAskUserQuestionFingerprint.slice(0,16)})),this.clearPromptState(n);return}let E=`synth-${(0,V.randomUUID)()}`;if(!E){this.promoteFingerprintAndClearPromptState(n),r.warn("Q[next] emit aborted: synthesized promptId was empty; promoted fingerprint + cleared prompt state",{sessionId:e,eventId:t.eventId});return}let k=n.pendingSynthTail??[],A=this.buildQuestionWireData(h,k),C=h.question,R={tool_name:"AskUserQuestion",tool_input:{questions:[h]},options:A.options,submitMap:A.submitMap,instructions:A.instructions},$=this.sessionKey,j=C,K=R,q=!1;$&&(j=p.cryptoService.encryptContent(C,$),K={encrypted:p.cryptoService.encryptMetadata(R,$)},q=!0);let Se=this.nextPromptGen++,Z={promptId:E,gen:Se};n.pendingPromptId=E,n.pendingSubmitMap=A.submitMap,n.promptGenerationToken=Z;let N=Z,G=this.activeSessions.get(e)?.promptGenerationToken;if(!G||G.gen!==N.gen||G.promptId!==N.promptId){r.warn("Q[next] emit aborted: token replaced before await dispatch",{sessionId:e,tokenAtAwait:N,currentToken:G});return}let re=E.length>0?E:`__prompt_gen_${Z.gen}`;n.inFlightPromptIds||(n.inFlightPromptIds=new Set),n.inFlightPromptIds.add(re);try{try{await this.appSyncClient.createEvent({sessionId:e,type:p.EventType.INTERACTIVE_PROMPT,source:p.EventSource.DESKTOP,content:j,metadata:K,promptId:E,isEncrypted:q?!0:void 0}),r.info("Q[next] emit succeeded",{sessionId:e,promptId:E,remaining:n.pendingQuestionsQueue?.length??0})}catch(x){let U=this.activeSessions.get(e),Q=U?.promptGenerationToken;Q&&Q.gen===N.gen&&Q.promptId===N.promptId?(this.promoteFingerprintAndClearPromptState(U),r.warn("Q[next] emit failed; promoted fingerprint + cleared prompt state. User must answer remaining questions on desktop terminal.",{sessionId:e,promptId:E,error:x instanceof Error?x.message:String(x)})):r.warn("Q[next] emit failed but a NEW prompt replaced our token during await; not clearing state (would wipe new prompt). Q[next..QN] of the original AskUserQuestion are lost; new prompt continues normally.",{sessionId:e,tokenAtAwait:N,currentToken:Q,error:x instanceof Error?x.message:String(x)})}}finally{n.inFlightPromptIds.delete(re)}}else try{await this.sendPromptError(e,"Failed to select option")}catch(w){r.warn("[walker] sendPromptError threw \u2014 relying on idempotent mark in finally",{sessionId:e,eventId:t.eventId,error:String(w)})}finally{await this.markEventExecutedIdempotent(n,t)}}else if(S.action==="option_with_followup"){if(n.pendingQuestionsQueue!==void 0){if(r.info("V2 walker \u2014 option_with_followup \u2192 cancel + new prompt",{sessionId:e,option:S.option,followUpText:S.followUpText}),await this.markEventExecutedIdempotent(n,t),P())return;if(await this.cancelV2WalkerAndExit(e,n,"option_with_followup",!!S.followUpText,P)&&S.followUpText){await new Promise(k=>setTimeout(k,1500));let h=this.activeSessions.get(e);if(h?.pendingPromptId||h?.waitingForPromptResponse){r.warn("Post-cancel followup suppressed: new prompt B is active",{sessionId:e,bPromptId:h.pendingPromptId});return}let E={...t,content:S.followUpText};await this.executeMobilePrompt(e,E)}return}let b=M?.[S.option]||S.option;r.info("User selected option with follow-up",{option:S.option,terminalInput:b,followUpText:S.followUpText});let I=await this.promptResponder.answerInteractivePrompt(e,b);if(P()){await this.markEventExecutedIdempotent(n,t);return}if(I){if(await this.markEventExecutedIdempotent(n,t),P())return;if(!m){r.warn("emitAnswerAck called without promptId \u2014 clearing state + skipping ack",{sessionId:e,source:"option_with_followup",eventId:t.eventId}),this.promoteFingerprintAndClearPromptState(n);return}try{await this.emitAnswerAck(e,`Selected option ${S.option}`,{promptId:m,questionIndex:0,isTerminal:!0})}catch(w){r.warn("[walker] emitAnswerAck (option_with_followup) failed \u2014 continuing to clearPromptState + executeMobilePrompt",{sessionId:e,promptId:m,error:w instanceof Error?w.message:String(w)})}if(P())return;if(this.promoteFingerprintAndClearPromptState(n),S.followUpText){await new Promise(h=>setTimeout(h,1e3));let w={...t,content:S.followUpText};await this.executeMobilePrompt(e,w)}}else try{await this.sendPromptError(e,"Failed to select option. Your reply (including the follow-up text) was not sent. Please retry.")}catch(w){r.warn("[walker] sendPromptError threw \u2014 relying on idempotent mark in finally",{sessionId:e,eventId:t.eventId,error:String(w)})}finally{await this.markEventExecutedIdempotent(n,t)}}else{if(n.pendingQuestionsQueue!==void 0){if(r.info("V2 walker \u2014 send_as_response \u2192 cancel + new prompt",{sessionId:e,contentPreview:c.slice(0,80)}),await this.markEventExecutedIdempotent(n,t),P())return;if(await this.cancelV2WalkerAndExit(e,n,"send_as_response",!0,P)){await new Promise(h=>setTimeout(h,1500));let w=this.activeSessions.get(e);if(w?.pendingPromptId||w?.waitingForPromptResponse){r.warn("Post-cancel free-text suppressed: new prompt B is active",{sessionId:e,bPromptId:w.pendingPromptId});return}await this.executeMobilePrompt(e,t)}return}r.info("Sending as free-form response to interactive prompt",{response:c});let b=await this.promptResponder.answerInteractivePrompt(e,c);if(P()){await this.markEventExecutedIdempotent(n,t);return}if(b){if(await this.markEventExecutedIdempotent(n,t),P())return;if(!m){r.warn("emitAnswerAck called without promptId \u2014 clearing state + skipping ack",{sessionId:e,source:"send_as_response",eventId:t.eventId}),this.promoteFingerprintAndClearPromptState(n);return}try{await this.emitAnswerAck(e,"Response sent to interactive prompt",{promptId:m,questionIndex:0,isTerminal:!0})}catch(I){r.warn("[walker] emitAnswerAck (send_as_response) failed \u2014 continuing to clearPromptState",{sessionId:e,promptId:m,error:I instanceof Error?I.message:String(I)})}if(P())return;this.promoteFingerprintAndClearPromptState(n)}else try{await this.sendPromptError(e,"Failed to send response")}catch(I){r.warn("[walker] sendPromptError threw \u2014 relying on idempotent mark in finally",{sessionId:e,eventId:t.eventId,error:String(I)})}finally{await this.markEventExecutedIdempotent(n,t)}}}finally{d&&n.inFlightPromptIds&&n.inFlightPromptIds.delete(d),g&&n.inFlightEventIds&&n.inFlightEventIds.delete(g)}}async sendInteractivePromptAsync(e,t,i){let n=this.activeSessions.get(e),s=n?.promptGenerationToken?{...n.promptGenerationToken}:void 0;await new Promise(v=>setTimeout(v,500));let o=process.env.CODEVIBE_TMUX_SESSION,a={...t.metadata||{}},l=t.metadata?.tool_name,u=t.metadata?.tool_input,d=l==="AskUserQuestion"&&Array.isArray(u?.questions)?u.questions:[],g=d.every(v=>!v.multiSelect),c=d.length>=1&&g;if(d.length>0&&Array.isArray(d[0]?.options)&&d[0].options.length>0){let v=d[0],b=[];if(o)try{let{exec:k}=await import("child_process"),A=R=>new Promise(($,j)=>{k(R,{timeout:5e3},(K,q)=>{K?j(K):$({stdout:q||""})})}),{stdout:C}=await A(`tmux capture-pane -p -e -S -30 -t '${o}'`);b=this.parseAskUserQuestionSynthTail(C),r.info("AskUserQuestion synth-tail parsed from tmux",{tailCount:b.length,tail:b.map(R=>R.label)})}catch(k){r.warn("Failed to capture tmux for AskUserQuestion synth-tail; emitting without synth tail",{error:k instanceof Error?k.message:String(k)})}else r.info("No tmux session \u2014 AskUserQuestion synth tail will be empty");let I=this.activeSessions.get(e);if(I){let k=I.promptGenerationToken;s&&k?.gen===s.gen?I.pendingSynthTail=b:r.warn("AskUserQuestion synth-tail: stale async \u2014 token gen mismatch, skipping pendingSynthTail write",{tokenAtEmit:s,currentToken:k,sessionId:e})}let w=this.buildQuestionWireData(v,b);a.options=JSON.parse(JSON.stringify(w.options)),a.submitMap=JSON.parse(JSON.stringify(w.submitMap)),a.instructions=w.instructions,a.tool_name="AskUserQuestion",a.tool_input={questions:[v]},i=v.question;let h=typeof t.prompt_id=="string"&&t.prompt_id.length>0,E=c&&h;if(E){let k=d.slice(1);k.push({question:"Ready to submit your answers?",options:[{label:"Submit answers",description:"Send your selections to the assistant"},{label:"Cancel",description:"Discard your answers"}],multiSelect:!1,_isSubmit:!0});let A=this.activeSessions.get(e);if(A){let C=A.promptGenerationToken;s&&C?.gen===s.gen?(A.pendingQuestionsQueue=k,A.pendingCurrentQuestion=v):r.warn("AskUserQuestion V2: stale async \u2014 token gen mismatch, skipping pendingQuestionsQueue write",{tokenAtEmit:s,currentToken:C,sessionId:e})}}else c&&!h&&r.warn("AskUserQuestion V2: empty prompt_id, degrading to single-Q legacy emit",{questionCount:d.length});r.info("AskUserQuestion V2: emitting Q1 only (Q2..QN queued)",{questionCount:d.length,v2SequentialEmit:E,queuedRemaining:E?d.length-1:0,optionCountFirst:w.options.length,anyMultiSelect:!g,questionPreview:v.question.slice(0,80)})}else if(o)try{let{exec:v}=await import("child_process"),b=E=>new Promise((k,A)=>{v(E,{timeout:5e3},(C,R)=>{C?A(C):k({stdout:R||""})})}),{stdout:I}=await b(`tmux capture-pane -p -e -S -30 -t '${o}'`),w=I.split(`
6
- `);r.info("tmux capture result",{tmuxSession:o,totalLines:w.length,lastLines:w.slice(-15).map(E=>E.replace(/\x1B[^m]*m/g,"").trim()).filter(Boolean)});let h=(0,p.parseInteractivePrompt)(I);h&&h.options.length>0?(a.options=h.options,a.submitMap=h.submitMap,a.instructions=this.buildPromptInstructions(h),r.info("Parsed dynamic options from tmux",{optionCount:h.options.length,kind:h.kind,options:h.options})):(r.info("No dynamic options parsed from tmux, using fallback",{parsedResult:h}),this.addFallbackOptions(a))}catch(v){r.warn("Failed to capture tmux pane for options",{error:v}),this.addFallbackOptions(a)}else r.warn("No tmux session \u2014 using fallback options"),this.addFallbackOptions(a);let m=this.activeSessions.get(e);if(m&&a.submitMap){let v=m.promptGenerationToken;s&&v?.gen===s.gen?m.pendingSubmitMap=a.submitMap:r.warn("Interactive prompt async: stale async \u2014 token gen mismatch, skipping pendingSubmitMap write",{tokenAtEmit:s,currentToken:v,sessionId:e})}let F=i,M=a,L=!1;this.sessionKey&&(F=p.cryptoService.encryptContent(i,this.sessionKey),M={encrypted:p.cryptoService.encryptMetadata(M,this.sessionKey)},L=!0);let P=this.activeSessions.get(e)?.promptGenerationToken;if(s&&P?.gen!==s.gen){r.warn("Interactive prompt emit: stale token \u2014 newer INTERACTIVE_PROMPT replaced ours; skipping AppSync emit",{sessionId:e,tokenAtEmit:s,currentToken:P});return}await this.appSyncClient.createEvent({sessionId:e,type:p.EventType.INTERACTIVE_PROMPT,source:t.source,content:F,metadata:M,promptId:t.prompt_id,isEncrypted:L?!0:void 0}),r.info("Interactive prompt sent to AppSync with dynamic options",{sessionId:e})}buildQuestionWireData(e,t=[]){let i=(e.options||[]).map((l,u)=>{let d=typeof l=="string",g=d?l:l.label||"",c=d?"":l.description||"",m=d?"":l.preview||"",F={number:String(u+1),text:g};return c&&(F.description=c),m&&(F.preview=m),F});if(!e._isSubmit)for(let l of t)i.push({number:String(i.length+1),text:l.label});let n=Object.fromEntries(i.map(l=>[l.number,l.number])),s=(e.options||[]).length,o=t.findIndex(l=>l.label==="Type something"),a;if(e._isSubmit)a="Reply with 1 to submit your answers or 2 to cancel.";else if(e.multiSelect)a=`Reply with comma-separated numbers (e.g., 1,3) for "${e.header||e.question}"`;else if(o>=0){let l=String(s+o+1);a=`Reply with the number of your choice. For option ${l} (Type something), reply "${l}, your answer".`}else a="Reply with the number of your choice.";return{options:i,submitMap:n,instructions:a}}parseAskUserQuestionSynthTail(e){let t=(0,p.normalizeSnapshot)(e);if(!t)return[];let i=t.split(`
7
- `).slice(-14),n=[],s=new Set,o=["Type something","Chat about this"];for(let a of i){let l=a.trim();for(let u of o)!s.has(u)&&l===u&&(n.push({label:u}),s.add(u))}return n}addFallbackOptions(e){e.options=[{number:"1",text:"Yes"},{number:"2",text:"Yes, and don't ask again"},{number:"3",text:"Reject and tell Claude what to do differently"}],e.submitMap={1:"1",2:"2",3:"3"},e.instructions="Reply with 1, 2, or 3. Append a message to provide alternative instructions."}buildPromptInstructions(e){return`Reply with ${e.options.map(i=>i.number).join(", ")}. Append a message to provide alternative instructions.`}parseInteractivePromptInput(e,t=3){return Ee(e,t)}async markEventExecuted(e){try{await this.appSyncClient.updateEventStatus({eventId:e.eventId,sessionId:e.sessionId,timestamp:e.timestamp,deliveryStatus:p.DeliveryStatus.EXECUTED}),r.info("Event marked as EXECUTED",{eventId:e.eventId})}catch(t){r.warn("Failed to mark event as EXECUTED",{eventId:e.eventId,error:t})}}async sendPromptError(e,t){let i={error:!0},n=t,s=i,o=!1;this.sessionKey&&(n=p.cryptoService.encryptContent(t,this.sessionKey),s={encrypted:p.cryptoService.encryptMetadata(i,this.sessionKey)},o=!0),await this.appSyncClient.createEvent({sessionId:e,type:p.EventType.NOTIFICATION,source:p.EventSource.DESKTOP,content:n,metadata:s,isEncrypted:o?!0:void 0})}async emitUserChoice(e,t){let i=t,n={source:"codevibe_v2_user_choice"},s=!1;this.sessionKey&&(i=p.cryptoService.encryptContent(t,this.sessionKey),n={encrypted:p.cryptoService.encryptMetadata({source:"codevibe_v2_user_choice"},this.sessionKey)},s=!0),await this.appSyncClient.createEvent({sessionId:e,type:p.EventType.ASSISTANT_RESPONSE,source:p.EventSource.DESKTOP,content:i,metadata:n,isEncrypted:s?!0:void 0})}async emitAnswerAck(e,t,i){let n={promptAnswered:!0,...i},s=t,o=n,a=!1;this.sessionKey&&(s=p.cryptoService.encryptContent(t,this.sessionKey),o={encrypted:p.cryptoService.encryptMetadata(n,this.sessionKey)},a=!0),await this.appSyncClient.createEvent({sessionId:e,type:p.EventType.NOTIFICATION,source:p.EventSource.DESKTOP,content:s,metadata:o,isEncrypted:a?!0:void 0})}promoteFingerprintAndClearPromptState(e){e.activeAskUserQuestionFingerprint&&(e.completedAskUserQuestionFingerprints||(e.completedAskUserQuestionFingerprints=new Set),e.completedAskUserQuestionFingerprints.add(e.activeAskUserQuestionFingerprint)),this.clearPromptState(e)}clearPromptState(e){e.waitingForPromptResponse=!1,e.pendingPromptId=void 0,e.pendingSubmitMap=void 0,e.pendingQuestionsQueue=void 0,e.pendingCurrentQuestion=void 0,e.pendingSynthTail=void 0,e.activeAskUserQuestionFingerprint=void 0,e.promptGenerationToken=void 0}async cancelV2WalkerAndExit(e,t,i,n,s){let o=t.pendingQuestionsQueue?.length??0;r.info("V2 walker mid-walker bailout \u2014 navigating to Submit/Cancel",{sessionId:e,advanceCount:o,triggerReason:i,hasFollowupText:n});let a=!1;for(let d=0;d<o;d++){let g=await this.promptResponder.sendKey(e,"Enter");if(s())return r.warn("cancelV2WalkerAndExit: aborted mid-loop (token rotated); leaving session state for new prompt",{sessionId:e,attemptedAdvances:d,totalAdvances:o,triggerReason:i}),!1;if(!g){a=!0,r.warn("cancelV2WalkerAndExit: sendKey returned false; aborting cancel sequence",{sessionId:e,attemptedAdvances:d,totalAdvances:o,triggerReason:i});break}if(await new Promise(c=>setTimeout(c,200)),s())return r.warn("cancelV2WalkerAndExit: aborted post-delay (token rotated); leaving session state",{sessionId:e,attemptedAdvances:d+1,totalAdvances:o,triggerReason:i}),!1}if(a){this.promoteFingerprintAndClearPromptState(t);try{await this.emitUserChoice(e,"AskUserQuestion bailout \u2014 desktop walker state unknown (tmux unavailable); your reply was not sent")}catch(d){r.warn("emitUserChoice on V2 cancel (sendKey failed) failed",{sessionId:e,error:d instanceof Error?d.message:String(d)})}return!1}let l=await this.promptResponder.answerInteractivePrompt(e,"2",{pressEnter:!0});if(s())return r.warn("cancelV2WalkerAndExit: aborted post-Cancel send (token rotated); leaving session state",{sessionId:e,triggerReason:i}),!1;if(!l){r.warn('cancelV2WalkerAndExit: final "2" Cancel send returned false; treating as bailout',{sessionId:e,triggerReason:i}),this.promoteFingerprintAndClearPromptState(t);try{await this.emitUserChoice(e,"AskUserQuestion bailout \u2014 Cancel keypress did not land (tmux unavailable); your reply was not sent")}catch(d){r.warn("emitUserChoice on V2 cancel (cancel send failed) failed",{sessionId:e,error:d instanceof Error?d.message:String(d)})}return!1}t.activeAskUserQuestionFingerprint&&(t.completedAskUserQuestionFingerprints||(t.completedAskUserQuestionFingerprints=new Set),t.completedAskUserQuestionFingerprints.add(t.activeAskUserQuestionFingerprint),r.info("V2 walker cancelled via mid-walker bailout \u2014 fingerprint marked closed",{sessionId:e,fingerprint:t.activeAskUserQuestionFingerprint.slice(0,16),triggerReason:i}));let u=n?"AskUserQuestion cancelled \u2014 sending your reply as a new prompt":"AskUserQuestion cancelled, no answers submitted";try{await this.emitUserChoice(e,u)}catch(d){r.warn("emitUserChoice on V2 cancel failed",{sessionId:e,error:d instanceof Error?d.message:String(d)})}return s()?(r.warn("cancelV2WalkerAndExit: aborted post-emitUserChoice (token rotated); leaving session state for new prompt",{sessionId:e,triggerReason:i}),!1):(this.clearPromptState(t),!0)}computeAskUserQuestionFingerprint(e){if(!(!e||typeof e!="object"))try{let t=this.stringifyCanonical(e);return(0,V.createHash)("sha256").update(t).digest("hex")}catch(t){r.warn("Failed to fingerprint AskUserQuestion questions",{error:t instanceof Error?t.message:String(t)});return}}stringifyCanonical(e){return e===null||typeof e!="object"?JSON.stringify(e):Array.isArray(e)?"["+e.map(i=>this.stringifyCanonical(i)).join(",")+"]":"{"+Object.keys(e).sort().map(i=>JSON.stringify(i)+":"+this.stringifyCanonical(e[i])).join(",")+"}"}async markEventExecutedIdempotent(e,t){e.processedEventIds||(e.processedEventIds=new Set),e.processedEventIds.add(t.eventId);try{await this.markEventExecuted(t)}catch(i){r.warn("[walker] markEventExecuted threw \u2014 relying on processedEventIds set for dedup",{sessionId:t.sessionId,eventId:t.eventId,error:String(i)})}}isSessionLimitExceeded(e){return this.getErrorMessage(e).includes("SESSION_LIMIT_EXCEEDED")}isUsageLimitExceeded(e){let t=this.getErrorMessage(e);return t.includes("MESSAGE_LIMIT_EXCEEDED")||t.includes("IMAGE_LIMIT_EXCEEDED")}getErrorMessage(e){if(e instanceof Error)return e.message;if(typeof e=="object"&&e!==null){let t=e;if(t.errors&&Array.isArray(t.errors))return t.errors.map(i=>i.message||"").join(" ");if(typeof t.message=="string")return t.message}return String(e)}displaySubscriptionLimitError(e,t){let i=this.getErrorMessage(e),n="",s=i.match(/for your (\w+) plan/i);s&&(n=` (${s[1]} tier)`);let o="",a=i.match(/of (\d+)/);switch(a&&(o=` [Limit: ${a[1]}]`),console.log(`
1
+ "use strict";var Ie=Object.create;var B=Object.defineProperty;var ke=Object.getOwnPropertyDescriptor;var Pe=Object.getOwnPropertyNames;var be=Object.getPrototypeOf,Ae=Object.prototype.hasOwnProperty;var Te=(f,e)=>{for(var t in e)B(f,t,{get:e[t],enumerable:!0})},pe=(f,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of Pe(e))!Ae.call(f,n)&&n!==t&&B(f,n,{get:()=>e[n],enumerable:!(i=ke(e,n))||i.enumerable});return f};var C=(f,e,t)=>(t=f!=null?Ie(be(f)):{},pe(e||!f||!f.__esModule?B(t,"default",{value:f,enumerable:!0}):t,f)),xe=f=>pe(B({},"__esModule",{value:!0}),f);var Re={};Te(Re,{McpServer:()=>J,parseInteractivePromptInput:()=>Se});module.exports=xe(Re);var M=C(require("fs")),N=C(require("path")),Y=C(require("os")),ve=require("child_process"),we=require("util"),Ee=require("child_process"),L=require("crypto");var ce=C(require("os")),de=C(require("path")),le=require("@quantiya/codevibe-core"),r=(0,le.createLogger)({name:"codevibe-claude",logFile:de.default.join(ce.default.tmpdir(),"codevibe-claude-mcp.log"),level:"info"});var a=require("@quantiya/codevibe-core");var te=C(require("express")),K=C(require("fs")),ne=C(require("path")),ie=C(require("os")),ue=require("@quantiya/codevibe-core");var v=require("@quantiya/codevibe-core");var H=class{constructor(){this.assignedPort=0;this.app=(0,te.default)(),this.setupMiddleware(),this.setupRoutes()}setSessionId(e){this.sessionId=e}getPort(){return this.assignedPort}setupMiddleware(){this.app.use(te.default.json({limit:"1mb"})),this.app.use((e,t,i)=>{r.debug(`${e.method} ${e.path}`,{body:e.body,query:e.query}),i()}),this.app.use((e,t,i,n)=>{r.error("Express error:",e);let s={success:!1,error:e.message||"Internal server error"};i.status(500).json(s)})}setupRoutes(){this.app.get("/health",this.handleHealth.bind(this)),this.app.post("/event",this.handleEvent.bind(this)),process.env.NODE_ENV!=="production"&&this.app.post("/test/execute",this.handleTestExecute.bind(this))}handleHealth(e,t){let i={success:!0,data:{status:"healthy",uptime:process.uptime(),version:"0.1.0",timestamp:new Date().toISOString()}};t.json(i)}async handleEvent(e,t){try{let i=e.body;if(!i.session_id){let o={success:!1,error:"Missing required field: session_id"};t.status(400).json(o);return}if(!i.hook_event_name){let o={success:!1,error:"Missing required field: hook_event_name"};t.status(400).json(o);return}let n=this.transformHookToEvent(i);r.info("Received event from hook",{sessionId:i.session_id,hookEvent:i.hook_event_name,type:n.type}),this.eventHandler?await this.eventHandler(n):r.warn("No event handler registered");let s={success:!0,message:"Event processed successfully"};t.json(s)}catch(i){r.error("Error handling event:",i);let n={success:!1,error:i instanceof Error?i.message:"Unknown error"};t.status(500).json(n)}}async handleTestExecute(e,t){try{let{sessionId:i,prompt:n}=e.body;if(!i||!n){let o={success:!1,error:"Missing required fields: sessionId, prompt"};t.status(400).json(o);return}r.info("Test execute request",{sessionId:i,prompt:n});let s={success:!0,message:"Test execution endpoint - not implemented yet",data:{sessionId:i,prompt:n}};t.json(s)}catch(i){r.error("Error in test execute:",i);let n={success:!1,error:i instanceof Error?i.message:"Unknown error"};t.status(500).json(n)}}transformHookToEvent(e){let t,i,n={cwd:e.cwd,hook_event_name:e.hook_event_name,...e.metadata||{}};if(e.type&&e.content!==void 0)t=e.type,i=e.content;else switch(e.hook_event_name){case"SessionStart":t=v.EventType.NOTIFICATION,i="Session started",n.source=e.source;break;case"SessionEnd":t=v.EventType.NOTIFICATION,i=`Session ended: ${e.reason||"unknown"}`,n.reason=e.reason;break;case"UserPromptSubmit":t=v.EventType.USER_PROMPT,i=e.prompt||"";break;case"PostToolUse":t=v.EventType.TOOL_USE,i=JSON.stringify({tool_name:e.tool_name,tool_input:e.tool_input,tool_response:e.tool_response}),n.tool_name=e.tool_name;break;case"Notification":t=v.EventType.NOTIFICATION,i=e.message||"",n.notification_type=e.notification_type;break;default:t=v.EventType.NOTIFICATION,i=`Hook event: ${e.hook_event_name}`}return{session_id:e.session_id,hook_event_name:e.hook_event_name,type:t,source:v.EventSource.DESKTOP,content:i,metadata:n}}onEvent(e){this.eventHandler=e}async start(e){let t=e||this.sessionId;return t&&(this.sessionId=t),new Promise((i,n)=>{try{let s=(0,ue.getConfig)(),o=s.server.dynamicPort?0:s.server.port;this.server=this.app.listen(o,s.server.host,()=>{let p=this.server.address();this.assignedPort=p.port,r.info(`HTTP API listening on http://${s.server.host}:${this.assignedPort}`),this.sessionId&&this.writePortFile(this.sessionId,this.assignedPort),i(this.assignedPort)}),this.server.on("error",p=>{r.error("HTTP server error:",p),n(p)})}catch(s){n(s)}})}writePortFile(e,t){let i=ne.join(ie.tmpdir(),`codevibe-claude-${e}.port`);try{K.writeFileSync(i,t.toString()),r.info(`Port file written: ${i} -> ${t}`)}catch(n){r.error(`Failed to write port file: ${i}`,n)}}removePortFile(){if(this.sessionId){let e=ne.join(ie.tmpdir(),`codevibe-claude-${this.sessionId}.port`);try{K.existsSync(e)&&(K.unlinkSync(e),r.info(`Port file removed: ${e}`))}catch(t){r.warn(`Failed to remove port file: ${e}`,t)}}}async stop(e){return new Promise((t,i)=>{this.sessionId&&e?.protectedSessionIds?.has(this.sessionId)?r.info("Skipping port file removal \u2014 another daemon still serves this session",{sessionId:this.sessionId}):this.removePortFile(),this.server?this.server.close(n=>{n?(r.error("Error stopping HTTP server:",n),i(n)):(r.info("HTTP API stopped"),t())}):t()})}};var me=require("child_process"),ge=require("@quantiya/codevibe-core");var X=class{async executePrompt(e,t){let i=(0,ge.getConfig)(),n=i.claude.defaultTimeout;return r.info("Executing prompt from mobile",{sessionId:e,promptLength:t.length,timeout:n}),new Promise(s=>{let o=["--resume",e,"--print","--output-format","stream-json",t];r.debug("Spawning Claude command",{command:i.claude.command,args:o});let p=(0,me.spawn)(i.claude.command,o,{stdio:["pipe","pipe","pipe"],shell:!0}),c="",u="",l=!1,m=setTimeout(()=>{l=!0,r.warn("Command execution timed out",{sessionId:e,timeout:n}),p.kill("SIGTERM")},n);p.stdout?.on("data",d=>{let g=d.toString();c+=g,r.debug("Command stdout",{output:g.slice(0,200)})}),p.stderr?.on("data",d=>{let g=d.toString();u+=g,r.debug("Command stderr",{output:g.slice(0,200)})}),p.on("close",d=>{clearTimeout(m);let g={success:d===0&&!l,output:c,error:u,exitCode:d||void 0,timedOut:l};g.success?r.info("Command executed successfully",{sessionId:e,exitCode:d,outputLength:c.length}):r.error("Command execution failed",{sessionId:e,exitCode:d,timedOut:l,error:u.slice(0,500)}),s(g)}),p.on("error",d=>{clearTimeout(m),r.error("Failed to spawn command",{error:d.message}),s({success:!1,error:d.message,timedOut:!1})})})}detectInteractivePrompt(e){return[/\[Y\/n\]/i,/\[y\/N\]/i,/\(y\/n\)/i,/Continue\?/i,/Proceed\?/i].some(i=>i.test(e))}extractPromptText(e){let t=e.split(`
2
+ `);for(let i=t.length-1;i>=0;i--){let n=t[i].trim();if(this.detectInteractivePrompt(n))return n}return null}};var fe=require("child_process"),he=require("util");var re=(0,he.promisify)(fe.exec),z=class{async answerInteractivePrompt(e,t,i={}){let{pressEnter:n=!0}=i;r.info("Attempting to answer interactive prompt",{sessionId:e,response:t,pressEnter:n});try{let s=process.env.CODEVIBE_TMUX_SESSION;return r.info("Checking tmux session environment",{tmuxSession:s||"(not set)",allEnvKeys:Object.keys(process.env).filter(o=>o.includes("CODEVIBE")||o.includes("TMUX"))}),s?(r.info("Using tmux send-keys",{tmuxSession:s,pressEnter:n}),await this.sendViaTmux(s,t,n),r.info("Successfully sent response to interactive prompt",{sessionId:e,response:t,pressEnter:n}),!0):(r.error("No tmux session found - codevibe-claude wrapper is required",{sessionId:e,hint:"Start Claude Code using the codevibe-claude wrapper script"}),!1)}catch(s){return r.error("Failed to answer interactive prompt",{sessionId:e,error:s instanceof Error?s.message:String(s)}),!1}}async sendViaTmux(e,t,i){let n=t.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\$/g,"\\$").replace(/`/g,"\\`");r.info("Sending via tmux",{sessionName:e,inputLength:t.length,pressEnter:i});try{let s=`tmux send-keys -t "${e}" -l "${n}"`,o=await re(s);if(r.info("tmux send-keys (text) completed",{stdout:o.stdout||"(empty)",stderr:o.stderr||"(empty)"}),i){await this.delay(500);let p=`tmux send-keys -t "${e}" Enter`,c=await re(p);r.info("tmux send-keys (Enter) completed",{stdout:c.stdout||"(empty)",stderr:c.stderr||"(empty)"})}else r.info("tmux send-keys: skipping Enter (caller requested digit-only)")}catch(s){throw r.error("tmux send-keys failed",{sessionName:e,error:s}),s}}async sendKey(e,t){let i=process.env.CODEVIBE_TMUX_SESSION;if(!i)return r.error("No tmux session found for sendKey",{sessionId:e,keyName:t}),!1;try{let n=`tmux send-keys -t "${i}" ${t}`,s=await re(n);return r.info("tmux send-keys (single key) completed",{sessionId:e,keyName:t,stdout:s.stdout||"(empty)",stderr:s.stderr||"(empty)"}),!0}catch(n){return r.error("tmux send-keys (single key) failed",{sessionId:e,keyName:t,error:n instanceof Error?n.message:String(n)}),!1}}delay(e){return new Promise(t=>setTimeout(t,e))}isPromptResponse(e){let t=e.trim().toLowerCase();return!!(t==="y"||t==="n"||t==="yes"||t==="no"||/^[0-9]+$/.test(t)||/^[a-z]$/.test(t)||["exit","quit","q","continue","skip","abort","retry","cancel"].includes(t))}};var Ce=(0,we.promisify)(Ee.exec),_e="/exit",ye="CODEVIBE_TMUX_SESSION";async function Fe(f,e){let t=async(i,n)=>{try{await Ce(i)}catch(s){r.warn("tmux send-keys failed during self-terminate",{sessionName:f,label:n,error:String(s)})}};await t(`tmux send-keys -t "${f}" C-c`,"ctrl-c"),await new Promise(i=>setTimeout(i,200)),await t(`tmux send-keys -t "${f}" -l "${e}"`,"quit-text"),await new Promise(i=>setTimeout(i,500)),await t(`tmux send-keys -t "${f}" Enter`,"enter")}var J=class f{constructor(e){this.activeSessions=new Map;this.assignedPort=0;this.sessionKey=null;this.claudeToBackendSessionId=new Map;this.pendingMobilePrompts=new Map;this.nextPromptGen=1;this.httpApi=new H,this.commandExecutor=new X,this.promptResponder=new z,this.initialSessionId=e}static{this.MOBILE_PROMPT_EXPIRY_MS=3e3}getPort(){return this.assignedPort}generateBackendSessionId(e){return`claude-${e}`}trackMobilePrompt(e,t){this.pendingMobilePrompts.has(e)||this.pendingMobilePrompts.set(e,[]),this.pendingMobilePrompts.get(e).push({prompt:t.trim(),timestamp:Date.now()}),r.debug("Tracking mobile prompt for deduplication",{sessionId:e,promptLength:t.length})}isRecentMobilePrompt(e,t){let i=this.pendingMobilePrompts.get(e);if(!i)return!1;let n=Date.now(),s=t.trim(),o=[],p=!1;for(let c of i)if(!(n-c.timestamp>f.MOBILE_PROMPT_EXPIRY_MS)){if(!p&&c.prompt===s){p=!0,r.debug("Found matching mobile prompt, filtering duplicate",{sessionId:e});continue}o.push(c)}return o.length>0?this.pendingMobilePrompts.set(e,o):this.pendingMobilePrompts.delete(e),p}writePortFile(e){let t=N.join(Y.tmpdir(),`codevibe-claude-${e}.port`);try{M.writeFileSync(t,this.assignedPort.toString()),r.info(`Port file written: ${t} -> ${this.assignedPort}`)}catch(i){r.error(`Failed to write port file: ${t}`,i)}}removePortFile(e){let t=N.join(Y.tmpdir(),`codevibe-claude-${e}.port`);try{M.existsSync(t)&&(M.unlinkSync(t),r.info(`Port file removed: ${t}`))}catch(i){r.warn(`Failed to remove port file: ${t}`,i)}}hasOtherLiveDaemonForSession(e){try{let t=(0,ve.execSync)("ps -eww -o pid= -o args=",{encoding:"utf8",timeout:2e3}),i=process.pid;for(let n of t.split(`
3
+ `)){let s=n.trim();if(!s)continue;let o=s.indexOf(" ");if(o<0)continue;let p=parseInt(s.substring(0,o),10);if(isNaN(p)||p===i)continue;let c=s.substring(o+1);if(/node.*codevibe-claude.*server\.js/.test(c)&&c.includes(e))return!0}return!1}catch(t){return r.warn('hasOtherLiveDaemonForSession: ps query failed; falling back to "no other daemon"',{error:String(t)}),!1}}async start(){try{if(r.info("Starting CodeVibe MCP Server...",{environment:(0,a.getEnvironment)()}),this.appSyncClient=new a.AppSyncClient,await this.appSyncClient.authenticateWithStoredTokens()){r.info("Authenticated with stored OAuth tokens",{userId:this.appSyncClient.getCurrentUserId(),email:this.appSyncClient.getCurrentUserEmail()}),await(0,a.registerDeviceEncryptionKey)(this.appSyncClient,r),(0,a.startDeviceKeyWatcher)(this.appSyncClient,r);try{let t=await this.appSyncClient.sweepOrphanSessions({agentType:"CLAUDE"});t>0&&r.info("Orphan sweep: marked stale Claude sessions INACTIVE",{swept:t})}catch(t){r.warn("Orphan sweep failed, continuing startup",{error:t instanceof Error?t.message:String(t)})}}else r.error('Authentication failed. Run "codevibe-claude login" first.'),console.error('Not authenticated. Run "codevibe-claude login" to sign in.'),process.exit(1);this.httpApi.onEvent(this.handleEventFromHook.bind(this)),this.assignedPort=await this.httpApi.start(this.initialSessionId),r.info("MCP Server started successfully",{port:this.assignedPort,host:(0,a.getConfig)().server.host,dynamicPort:(0,a.getConfig)().server.dynamicPort,sessionId:this.initialSessionId,authenticated:this.appSyncClient.isAuthenticated(),userId:this.appSyncClient.getCurrentUserId()})}catch(e){throw r.error("Failed to start MCP Server:",e),e}}async stop(){r.info("Stopping MCP Server...");let e=Array.from(this.activeSessions.keys()),t=new Set;r.info(`Marking ${e.length} active session(s) as INACTIVE...`);for(let i of e){let n=this.activeSessions.get(i);n?.mobileEndWatcher&&(n.mobileEndWatcher.stop(),n.mobileEndWatcher=void 0)}for(let i of e)try{let n=this.activeSessions.get(i);if(n&&this.hasOtherLiveDaemonForSession(n.claudeSessionId)){r.info("Another daemon serves this session \u2014 skipping mark INACTIVE AND port file removal during shutdown",{sessionId:i,claudeSessionId:n.claudeSessionId,myPid:process.pid}),t.add(n.claudeSessionId);continue}await this.appSyncClient.updateSession({sessionId:i,status:a.SessionStatus.INACTIVE}),r.info("Session marked as INACTIVE during shutdown",{sessionId:i}),n&&this.removePortFile(n.claudeSessionId)}catch(n){r.warn("Failed to mark session as INACTIVE during shutdown",{sessionId:i,error:n})}this.appSyncClient.cleanupSubscriptions(),this.activeSessions.clear(),await this.httpApi.stop({protectedSessionIds:t}),r.info("MCP Server stopped")}async handleEventFromHook(e){let{session_id:t,hook_event_name:i,type:n,content:s}=e;r.info("Processing hook event",{sessionId:t,hookEvent:i,type:n});try{i==="SessionStart"?await this.handleSessionStart(e):i==="SessionEnd"&&await this.handleSessionEnd(e);let o=this.claudeToBackendSessionId.get(t)||this.generateBackendSessionId(t);if(i==="UserPromptSubmit"){let m=this.activeSessions.get(o);if(m?.completedAskUserQuestionFingerprints?.size){let d=m.completedAskUserQuestionFingerprints.size;m.completedAskUserQuestionFingerprints.clear(),r.info("Turn boundary \u2014 cleared closed-AskUserQuestion fingerprints",{sessionId:o,clearedCount:d})}}if(n===a.EventType.USER_PROMPT&&e.source===a.EventSource.DESKTOP&&i==="UserPromptSubmit"&&s&&this.isRecentMobilePrompt(o,s)){r.info("Skipping duplicate USER_PROMPT from mobile-originated prompt",{sessionId:o,contentLength:s.length});return}if(n===a.EventType.INTERACTIVE_PROMPT){(typeof e.prompt_id!="string"||e.prompt_id.length===0)&&(e.prompt_id=`synth-${(0,L.randomUUID)()}`,r.info("Synthesized prompt_id for INTERACTIVE_PROMPT (hook omitted it)",{sessionId:o,synthesizedPromptId:e.prompt_id}));let m=this.activeSessions.get(o),d;if(m&&e.metadata?.tool_name==="AskUserQuestion"&&(d=this.computeAskUserQuestionFingerprint(e.metadata.tool_input?.questions),d)){let g=m.activeAskUserQuestionFingerprint===d,_=m.completedAskUserQuestionFingerprints?.has(d)??!1;if(g||_){r.info("Dropping duplicate INTERACTIVE_PROMPT \u2014 AskUserQuestion already tracked",{sessionId:o,fingerprint:d.slice(0,16),status:g?"in-flight":"completed",hookEvent:e.hook_event_name,promptId:e.prompt_id});return}}if(m){this.clearPromptState(m),m.waitingForPromptResponse=!0,m.pendingPromptId=e.prompt_id;let g=this.nextPromptGen++;m.promptGenerationToken={promptId:e.prompt_id||"",gen:g},d&&(m.activeAskUserQuestionFingerprint=d),r.info("Interactive prompt detected - will parse options from tmux",{sessionId:o,promptId:e.prompt_id,tokenGen:g,askUserQuestionFingerprint:d?.slice(0,16)})}this.sendInteractivePromptAsync(o,e,s).catch(g=>{r.error("Failed to send interactive prompt with dynamic options",{error:g})});return}let p=s,c=e.metadata,u=!1;r.info("Hook event encryption state",{type:n,sessionId:o,hasSessionKey:!!this.sessionKey,sessionKeyLength:this.sessionKey?.length||0}),this.sessionKey?(p=a.cryptoService.encryptContent(s,this.sessionKey),c&&(c={encrypted:a.cryptoService.encryptMetadata(c,this.sessionKey)}),u=!0,r.info("Event encrypted for hook",{type:n,sessionId:o,isEncrypted:!0})):r.warn("No session key - event will NOT be encrypted",{type:n,sessionId:o});let l=await this.appSyncClient.createEvent({sessionId:o,type:n,source:e.source,content:p,metadata:c,promptId:e.prompt_id,timestamp:(0,a.prepareEventTimestamp)({orderingKey:o}),isEncrypted:u?!0:void 0});if(n===a.EventType.USER_PROMPT&&e.source===a.EventSource.DESKTOP){let m=this.activeSessions.get(o);m?.waitingForPromptResponse&&(this.promoteFingerprintAndClearPromptState(m),r.info("Clearing prompt wait state - new desktop prompt received",{sessionId:o}))}r.debug("Event sent to AppSync successfully")}catch(o){throw r.error("Failed to process hook event:",o),o}}async handleSessionStart(e){let t=e.session_id,i=this.generateBackendSessionId(t),n=e.metadata?.cwd||process.cwd();this.claudeToBackendSessionId.set(t,i),r.info("Session started",{claudeSessionId:t,sessionId:i,cwd:n});let s=Array.from(this.activeSessions.keys()).filter(u=>u!==i);if(s.length>0){r.info(`Marking ${s.length} previous session(s) as INACTIVE`);for(let u of s){let l=this.activeSessions.get(u);l?.mobileEndWatcher&&(l.mobileEndWatcher.stop(),l.mobileEndWatcher=void 0);try{await this.appSyncClient.updateSession({sessionId:u,status:a.SessionStatus.INACTIVE}),r.info("Previous session marked INACTIVE",{prevId:u,newSessionId:i})}catch(m){r.warn("Failed to mark previous session as INACTIVE",{prevId:u,error:m})}l&&this.removePortFile(l.claudeSessionId),this.activeSessions.delete(u)}}this.writePortFile(t);let o=this.appSyncClient.getCurrentUserId(),p={sessionId:i,claudeSessionId:t,userId:o,projectPath:n,cwd:n,createdAt:new Date,subscriptionActive:!1,waitingForPromptResponse:!1,metadata:e.metadata||{}};this.activeSessions.set(i,p);try{let u=await(0,a.resumeOrCreateSession)({sessionId:i,userId:p.userId,agentType:a.AgentType.CLAUDE,projectPath:n,metadata:e.metadata||{}},this.appSyncClient,r);if(this.sessionKey=u.sessionKey,u.resumed&&!u.sessionKey){let l=await a.keychainManager.getDeviceId();r.error("Device key not found in session encryptedKeys",{sessionId:i,pluginDeviceId:l}),console.error(`
4
+ \u26A0\uFE0F E2E ENCRYPTION WARNING: Cannot decrypt this session!`),console.error(` Your device ID (${l.substring(0,8)}...) is not in session's encryption keys.`),console.error(" This happens if your device key was regenerated after the session was created."),console.error(` SOLUTION: Start a new Claude Code session instead of resuming this one.
5
+ `)}}catch(u){if(this.isSessionLimitExceeded(u)){this.displaySubscriptionLimitError(u,"session"),this.activeSessions.delete(i),this.removePortFile(t);return}r.error("Failed to create/resume session:",u)}this.subscribeToMobileEvents(i),this.appSyncClient.startHeartbeat(i);let c=this.activeSessions.get(i);c&&(c.mobileEndWatcher=this.appSyncClient.watchForMobileEnd(i,async()=>{r.info("Mobile ended session \u2014 sending desktop quit",{sessionId:i});let u=process.env[ye];if(!u){r.warn("No tmux session set; skipping desktop self-terminate",{sessionId:i,expectedEnv:ye});return}await Fe(u,_e)}))}async handleSessionEnd(e){let t=e.session_id,i=this.claudeToBackendSessionId.get(t)||this.generateBackendSessionId(t);r.info("Session ended",{claudeSessionId:t,sessionId:i,reason:e.metadata?.reason});let n=this.activeSessions.get(i);if(n?.mobileEndWatcher&&(n.mobileEndWatcher.stop(),n.mobileEndWatcher=void 0),this.removePortFile(t),n?.waitingForPromptResponse&&(r.info("Clearing prompt wait state - session ending",{sessionId:i}),this.clearPromptState(n)),this.appSyncClient.stopHeartbeat(i),n)try{await this.appSyncClient.updateSession({sessionId:i,status:a.SessionStatus.INACTIVE}),r.info("Session marked as INACTIVE in AppSync",{sessionId:i})}catch(s){r.warn("Failed to update session in AppSync:",s)}else r.warn("Cannot update session - session state not found",{sessionId:i});this.activeSessions.delete(i),this.claudeToBackendSessionId.delete(t),r.debug("Session cleanup completed",{sessionId:i})}subscribeToMobileEvents(e){r.info("Subscribing to mobile events",{sessionId:e});let t=this.activeSessions.get(e);if(!t){r.error("Session not found",{sessionId:e});return}this.appSyncClient.subscribeToEvents(e,async i=>{await this.dispatchMobileEvent(e,i)},i=>{r.error("Subscription error",{sessionId:e,error:i})}),t.subscriptionActive=!0,r.info("Subscription active",{sessionId:e})}async dispatchMobileEvent(e,t){r.info("Received mobile event",{eventId:t.eventId,type:t.type,sessionId:t.sessionId,isEncrypted:t.isEncrypted});let i,n,s=!1,o,p,c;if(t.type===a.EventType.USER_PROMPT||t.type===a.EventType.PROMPT_RESPONSE)if(n=this.activeSessions.get(e),!n)i="no-session";else if(n.processedEventIds?.has(t.eventId))i="skip-dedup";else if(n.inFlightEventIds?.has(t.eventId))i="drop-event-redeliver";else if(n.waitingForPromptResponse){let d=n.promptGenerationToken;if(!d)i="regular";else if(t.type===a.EventType.USER_PROMPT&&n.hasReceivedPromptResponse&&(!t.promptId||t.promptId.length===0))i="drop-stale-answer";else if(t.promptId&&t.promptId.length>0&&d.promptId.length>0&&t.promptId!==d.promptId)i="drop-stale-answer";else{let g=d.promptId.length>0?d.promptId:`__prompt_gen_${d.gen}`;n.inFlightPromptIds?.has(g)?i="drop-in-flight":(n.inFlightPromptIds||(n.inFlightPromptIds=new Set),n.inFlightEventIds||(n.inFlightEventIds=new Set),n.inFlightPromptIds.add(g),n.inFlightEventIds.add(t.eventId),s=!0,p=g,c=t.eventId,o={promptId:d.promptId,gen:d.gen},i="walker")}}else i="regular";else i="not-user-prompt";let l=t.content||"";if(t.isEncrypted&&this.sessionKey)try{l=a.cryptoService.decryptContent(t.content,this.sessionKey),r.debug("Event decrypted successfully",{eventId:t.eventId})}catch(d){r.error("Failed to decrypt event:",{eventId:t.eventId,error:d}),l=t.content}let m={...t,content:l};if(i!=="skip-dedup")try{await this.appSyncClient.updateEventStatus({eventId:t.eventId,sessionId:t.sessionId,timestamp:t.timestamp,deliveryStatus:a.DeliveryStatus.DELIVERED}),r.info("Event marked as DELIVERED",{eventId:t.eventId})}catch(d){r.warn("Failed to mark event as DELIVERED",{eventId:t.eventId,error:d})}if(i==="skip-dedup"){r.info("[walker] Subscription-level dedup hit (already processed) \u2014 skipping",{sessionId:e,eventId:t.eventId});return}if(i==="drop-stale-answer"){r.info("[walker] Stale answer dropped \u2014 event.promptId does not match current pending promptId",{sessionId:e,eventId:t.eventId,eventPromptId:t.promptId,currentPromptId:n?.promptGenerationToken?.promptId}),n&&(n.processedEventIds||(n.processedEventIds=new Set),n.processedEventIds.add(t.eventId));try{await this.markEventExecuted(t)}catch(d){r.warn("[walker] markEventExecuted threw on stale-answer drop \u2014 relying on processedEventIds Set",{sessionId:e,eventId:t.eventId,error:String(d)})}return}if(i==="drop-in-flight"){r.warn("[walker] Subscription-level in-flight guard \u2014 dropping duplicate USER_PROMPT (different eventId, same prompt)",{sessionId:e,eventId:t.eventId}),n&&(n.processedEventIds||(n.processedEventIds=new Set),n.processedEventIds.add(t.eventId));try{await this.markEventExecuted(t)}catch(d){r.warn("[walker] markEventExecuted threw on subscription-level duplicate drop \u2014 relying on processedEventIds Set",{sessionId:e,eventId:t.eventId,error:String(d)})}return}if(i==="drop-event-redeliver"){r.info("[walker] Subscription-level event-level redelivery \u2014 silent skip (original still in flight)",{sessionId:e,eventId:t.eventId});return}if(i==="walker"){t.type===a.EventType.PROMPT_RESPONSE&&n&&(n.hasReceivedPromptResponse=!0),await this.handleMobilePromptResponse(e,t,l,n,s,o,p,c);return}if(i==="regular"){if(t.type===a.EventType.PROMPT_RESPONSE){r.warn("Received PROMPT_RESPONSE with no active walker \u2014 dropping",{sessionId:e,eventId:t.eventId,promptId:t.promptId}),n&&(n.processedEventIds||(n.processedEventIds=new Set),n.processedEventIds.add(t.eventId));try{await this.markEventExecuted(t)}catch(d){r.warn("markEventExecuted threw on PROMPT_RESPONSE orphan drop \u2014 relying on processedEventIds Set",{sessionId:e,eventId:t.eventId,error:String(d)})}return}await this.executeMobilePrompt(e,m);return}if(i==="no-session"){r.warn("Received mobile prompt input for unknown session \u2014 ignoring",{sessionId:e,eventId:t.eventId,type:t.type});return}}async handleMobilePromptResponse(e,t,i,n,s=!1,o,p,c){let u=o??n.promptGenerationToken,l=p,m=c;if(!s&&u){let d=u.promptId.length>0?u.promptId:`__prompt_gen_${u.gen}`;if(n.inFlightPromptIds?.has(d)){r.warn("[walker] Duplicate mobile USER_PROMPT for same prompt \u2014 dropping",{sessionId:e,eventId:t.eventId,lockKey:d}),await this.markEventExecutedIdempotent(n,t);return}n.inFlightPromptIds||(n.inFlightPromptIds=new Set),n.inFlightEventIds||(n.inFlightEventIds=new Set),n.inFlightPromptIds.add(d),n.inFlightEventIds.add(t.eventId),l=d,m=t.eventId}try{if(!s&&n.processedEventIds?.has(t.eventId)){r.info("[walker] Redelivered event already processed \u2014 skipping",{sessionId:e,eventId:t.eventId});return}let d=i.trim(),g=n.pendingPromptId,U=n.pendingSubmitMap,$=U?Object.keys(U).length:3,S=this.parseInteractivePromptInput(d,$);r.info("Parsed interactive prompt input",{sessionId:e,content:d,parsed:S,hasSubmitMap:!!U});let P=()=>{let T=n.promptGenerationToken,h=T?.gen,I=u?.gen;return h!==I?(r.warn("[walker] Token mismatch \u2014 external cleanup or new prompt during in-flight handler \u2014 aborting",{sessionId:e,eventId:t.eventId,entryToken:u,currentToken:T}),!0):!1};if(P()){await this.markEventExecutedIdempotent(n,t);return}{let T=n.pendingQuestionsQueue!==void 0,I=d.trim().match(/^(\d+)$/);if(T&&n.pendingCurrentQuestion&&I){let w=n.pendingCurrentQuestion.options?.length??0,E=I[1],y=parseInt(E,10),b=!Number.isFinite(y)||y<1||y>w,k=String(y)!==E;if(b||k){if(r.info("V2 walker \u2014 bare out-of-range or non-canonical option; routing to cancel",{sessionId:e,option:E,optionNum:y,realOptionCount:w,isOutOfRange:b,isNonCanonical:k,isSubmitStep:!!n.pendingCurrentQuestion._isSubmit,parsedAction:S.action}),await this.markEventExecutedIdempotent(n,t),P())return;await this.cancelV2WalkerAndExit(e,n,"invalid_option",!1,P);return}}}if(S.action==="select_option"){let T=U?.[S.option]||S.option,h=n.pendingQuestionsQueue!==void 0;r.info("User selected option",{option:S.option,terminalInput:T,isV2AskUserQuestion:h});let I=await this.promptResponder.answerInteractivePrompt(e,T,{pressEnter:!0});if(P()){await this.markEventExecutedIdempotent(n,t);return}if(I){if(await this.markEventExecutedIdempotent(n,t),P())return;if(!g){r.warn("emitAnswerAck called without promptId \u2014 clearing state + skipping ack",{sessionId:e,source:"select_option",eventId:t.eventId}),this.promoteFingerprintAndClearPromptState(n);return}let w=(n.pendingQuestionsQueue?.length??0)===0;try{if(h){let A=parseInt(S.option,10)-1,Q=n.pendingCurrentQuestion?.options?.[A],O=typeof Q=="string"?Q:Q&&typeof Q=="object"?Q.label:`option ${S.option}`,oe=n.pendingCurrentQuestion?._isSubmit===!0,ae=O.toLowerCase(),W;oe&&ae==="cancel"?W="\u2192 Cancel \u2014 AskUserQuestion cancelled, no answers submitted":oe&&ae.startsWith("submit")?W="\u2192 Submit answers \u2014 AskUserQuestion completed":W=`\u2192 ${O}`,await this.emitUserChoice(e,W)}else await this.emitAnswerAck(e,`Selected option ${S.option}`,{promptId:g,questionIndex:0,isTerminal:w})}catch(A){r.warn("[walker] user-choice/ack emit failed \u2014 continuing to STEP 7/8",{sessionId:e,promptId:g,isV2AskUserQuestion:h,error:A instanceof Error?A.message:String(A)})}if(P())return;let E=n.pendingQuestionsQueue?.shift();if(E&&(n.pendingCurrentQuestion=E),!E){n.activeAskUserQuestionFingerprint&&(n.completedAskUserQuestionFingerprints||(n.completedAskUserQuestionFingerprints=new Set),n.completedAskUserQuestionFingerprints.add(n.activeAskUserQuestionFingerprint),r.info("AskUserQuestion V2 walker complete \u2014 fingerprint marked closed",{sessionId:e,fingerprint:n.activeAskUserQuestionFingerprint.slice(0,16)})),this.clearPromptState(n);return}let y=`synth-${(0,L.randomUUID)()}`;if(!y){this.promoteFingerprintAndClearPromptState(n),r.warn("Q[next] emit aborted: synthesized promptId was empty; promoted fingerprint + cleared prompt state",{sessionId:e,eventId:t.eventId});return}let b=n.pendingSynthTail??[],k=this.buildQuestionWireData(E,b),x=E.question,F={tool_name:"AskUserQuestion",tool_input:{questions:[E]},options:k.options,submitMap:k.submitMap,instructions:k.instructions},R=this.sessionKey,j=x,q=F,V=!1;R&&(j=a.cryptoService.encryptContent(x,R),q={encrypted:a.cryptoService.encryptMetadata(F,R)},V=!0);let Z=this.nextPromptGen++,ee={promptId:y,gen:Z};n.pendingPromptId=y,n.pendingSubmitMap=k.submitMap,n.promptGenerationToken=ee;let D=ee,G=this.activeSessions.get(e)?.promptGenerationToken;if(!G||G.gen!==D.gen||G.promptId!==D.promptId){r.warn("Q[next] emit aborted: token replaced before await dispatch",{sessionId:e,tokenAtAwait:D,currentToken:G});return}let se=y.length>0?y:`__prompt_gen_${ee.gen}`;n.inFlightPromptIds||(n.inFlightPromptIds=new Set),n.inFlightPromptIds.add(se);try{try{await this.appSyncClient.createEvent({sessionId:e,type:a.EventType.INTERACTIVE_PROMPT,source:a.EventSource.DESKTOP,content:j,metadata:q,promptId:y,timestamp:(0,a.prepareEventTimestamp)({orderingKey:e}),isEncrypted:V?!0:void 0}),r.info("Q[next] emit succeeded",{sessionId:e,promptId:y,remaining:n.pendingQuestionsQueue?.length??0})}catch(A){let Q=this.activeSessions.get(e),O=Q?.promptGenerationToken;O&&O.gen===D.gen&&O.promptId===D.promptId?(this.promoteFingerprintAndClearPromptState(Q),r.warn("Q[next] emit failed; promoted fingerprint + cleared prompt state. User must answer remaining questions on desktop terminal.",{sessionId:e,promptId:y,error:A instanceof Error?A.message:String(A)})):r.warn("Q[next] emit failed but a NEW prompt replaced our token during await; not clearing state (would wipe new prompt). Q[next..QN] of the original AskUserQuestion are lost; new prompt continues normally.",{sessionId:e,tokenAtAwait:D,currentToken:O,error:A instanceof Error?A.message:String(A)})}}finally{n.inFlightPromptIds.delete(se)}}else try{await this.sendPromptError(e,"Failed to select option")}catch(w){r.warn("[walker] sendPromptError threw \u2014 relying on idempotent mark in finally",{sessionId:e,eventId:t.eventId,error:String(w)})}finally{await this.markEventExecutedIdempotent(n,t)}}else if(S.action==="option_with_followup"){if(n.pendingQuestionsQueue!==void 0){if(r.info("V2 walker \u2014 option_with_followup \u2192 cancel + new prompt",{sessionId:e,option:S.option,followUpText:S.followUpText}),await this.markEventExecutedIdempotent(n,t),P())return;if(await this.cancelV2WalkerAndExit(e,n,"option_with_followup",!!S.followUpText,P)&&S.followUpText){await new Promise(b=>setTimeout(b,1500));let E=this.activeSessions.get(e);if(E?.pendingPromptId||E?.waitingForPromptResponse){r.warn("Post-cancel followup suppressed: new prompt B is active",{sessionId:e,bPromptId:E.pendingPromptId});return}let y={...t,content:S.followUpText};await this.executeMobilePrompt(e,y)}return}let h=U?.[S.option]||S.option;r.info("User selected option with follow-up",{option:S.option,terminalInput:h,followUpText:S.followUpText});let I=await this.promptResponder.answerInteractivePrompt(e,h);if(P()){await this.markEventExecutedIdempotent(n,t);return}if(I){if(await this.markEventExecutedIdempotent(n,t),P())return;if(!g){r.warn("emitAnswerAck called without promptId \u2014 clearing state + skipping ack",{sessionId:e,source:"option_with_followup",eventId:t.eventId}),this.promoteFingerprintAndClearPromptState(n);return}try{await this.emitAnswerAck(e,`Selected option ${S.option}`,{promptId:g,questionIndex:0,isTerminal:!0})}catch(w){r.warn("[walker] emitAnswerAck (option_with_followup) failed \u2014 continuing to clearPromptState + executeMobilePrompt",{sessionId:e,promptId:g,error:w instanceof Error?w.message:String(w)})}if(P())return;if(this.promoteFingerprintAndClearPromptState(n),S.followUpText){await new Promise(E=>setTimeout(E,1e3));let w={...t,content:S.followUpText};await this.executeMobilePrompt(e,w)}}else try{await this.sendPromptError(e,"Failed to select option. Your reply (including the follow-up text) was not sent. Please retry.")}catch(w){r.warn("[walker] sendPromptError threw \u2014 relying on idempotent mark in finally",{sessionId:e,eventId:t.eventId,error:String(w)})}finally{await this.markEventExecutedIdempotent(n,t)}}else{if(n.pendingQuestionsQueue!==void 0){if(r.info("V2 walker \u2014 send_as_response \u2192 cancel + new prompt",{sessionId:e,contentPreview:d.slice(0,80)}),await this.markEventExecutedIdempotent(n,t),P())return;if(await this.cancelV2WalkerAndExit(e,n,"send_as_response",!0,P)){await new Promise(E=>setTimeout(E,1500));let w=this.activeSessions.get(e);if(w?.pendingPromptId||w?.waitingForPromptResponse){r.warn("Post-cancel free-text suppressed: new prompt B is active",{sessionId:e,bPromptId:w.pendingPromptId});return}await this.executeMobilePrompt(e,t)}return}r.info("Sending as free-form response to interactive prompt",{response:d});let h=await this.promptResponder.answerInteractivePrompt(e,d);if(P()){await this.markEventExecutedIdempotent(n,t);return}if(h){if(await this.markEventExecutedIdempotent(n,t),P())return;if(!g){r.warn("emitAnswerAck called without promptId \u2014 clearing state + skipping ack",{sessionId:e,source:"send_as_response",eventId:t.eventId}),this.promoteFingerprintAndClearPromptState(n);return}try{await this.emitAnswerAck(e,"Response sent to interactive prompt",{promptId:g,questionIndex:0,isTerminal:!0})}catch(I){r.warn("[walker] emitAnswerAck (send_as_response) failed \u2014 continuing to clearPromptState",{sessionId:e,promptId:g,error:I instanceof Error?I.message:String(I)})}if(P())return;this.promoteFingerprintAndClearPromptState(n)}else try{await this.sendPromptError(e,"Failed to send response")}catch(I){r.warn("[walker] sendPromptError threw \u2014 relying on idempotent mark in finally",{sessionId:e,eventId:t.eventId,error:String(I)})}finally{await this.markEventExecutedIdempotent(n,t)}}}finally{l&&n.inFlightPromptIds&&n.inFlightPromptIds.delete(l),m&&n.inFlightEventIds&&n.inFlightEventIds.delete(m)}}async sendInteractivePromptAsync(e,t,i){let n=this.activeSessions.get(e),s=n?.promptGenerationToken?{...n.promptGenerationToken}:void 0,o=(0,a.prepareEventTimestamp)({orderingKey:e});await new Promise(h=>setTimeout(h,500));let p=process.env.CODEVIBE_TMUX_SESSION,c={...t.metadata||{}},u=t.metadata?.tool_name,l=t.metadata?.tool_input,m=u==="AskUserQuestion"&&Array.isArray(l?.questions)?l.questions:[],d=m.every(h=>!h.multiSelect),g=m.length>=1&&d;if(m.length>0&&Array.isArray(m[0]?.options)&&m[0].options.length>0){let h=m[0],I=[];if(p)try{let{exec:k}=await import("child_process"),x=R=>new Promise((j,q)=>{k(R,{timeout:5e3},(V,Z)=>{V?q(V):j({stdout:Z||""})})}),{stdout:F}=await x(`tmux capture-pane -p -e -S -30 -t '${p}'`);I=this.parseAskUserQuestionSynthTail(F),r.info("AskUserQuestion synth-tail parsed from tmux",{tailCount:I.length,tail:I.map(R=>R.label)})}catch(k){r.warn("Failed to capture tmux for AskUserQuestion synth-tail; emitting without synth tail",{error:k instanceof Error?k.message:String(k)})}else r.info("No tmux session \u2014 AskUserQuestion synth tail will be empty");let w=this.activeSessions.get(e);if(w){let k=w.promptGenerationToken;s&&k?.gen===s.gen?w.pendingSynthTail=I:r.warn("AskUserQuestion synth-tail: stale async \u2014 token gen mismatch, skipping pendingSynthTail write",{tokenAtEmit:s,currentToken:k,sessionId:e})}let E=this.buildQuestionWireData(h,I);c.options=JSON.parse(JSON.stringify(E.options)),c.submitMap=JSON.parse(JSON.stringify(E.submitMap)),c.instructions=E.instructions,c.tool_name="AskUserQuestion",c.tool_input={questions:[h]},i=h.question;let y=typeof t.prompt_id=="string"&&t.prompt_id.length>0,b=g&&y;if(b){let k=m.slice(1);k.push({question:"Ready to submit your answers?",options:[{label:"Submit answers",description:"Send your selections to the assistant"},{label:"Cancel",description:"Discard your answers"}],multiSelect:!1,_isSubmit:!0});let x=this.activeSessions.get(e);if(x){let F=x.promptGenerationToken;s&&F?.gen===s.gen?(x.pendingQuestionsQueue=k,x.pendingCurrentQuestion=h):r.warn("AskUserQuestion V2: stale async \u2014 token gen mismatch, skipping pendingQuestionsQueue write",{tokenAtEmit:s,currentToken:F,sessionId:e})}}else g&&!y&&r.warn("AskUserQuestion V2: empty prompt_id, degrading to single-Q legacy emit",{questionCount:m.length});r.info("AskUserQuestion V2: emitting Q1 only (Q2..QN queued)",{questionCount:m.length,v2SequentialEmit:b,queuedRemaining:b?m.length-1:0,optionCountFirst:E.options.length,anyMultiSelect:!d,questionPreview:h.question.slice(0,80)})}else if(p)try{let{exec:h}=await import("child_process"),I=b=>new Promise((k,x)=>{h(b,{timeout:5e3},(F,R)=>{F?x(F):k({stdout:R||""})})}),{stdout:w}=await I(`tmux capture-pane -p -e -S -30 -t '${p}'`),E=w.split(`
6
+ `);r.info("tmux capture result",{tmuxSession:p,totalLines:E.length,lastLines:E.slice(-15).map(b=>b.replace(/\x1B[^m]*m/g,"").trim()).filter(Boolean)});let y=(0,a.parseInteractivePrompt)(w);y&&y.options.length>0?(c.options=y.options,c.submitMap=y.submitMap,c.instructions=this.buildPromptInstructions(y),r.info("Parsed dynamic options from tmux",{optionCount:y.options.length,kind:y.kind,options:y.options})):(r.info("No dynamic options parsed from tmux, using fallback",{parsedResult:y}),this.addFallbackOptions(c))}catch(h){r.warn("Failed to capture tmux pane for options",{error:h}),this.addFallbackOptions(c)}else r.warn("No tmux session \u2014 using fallback options"),this.addFallbackOptions(c);let _=this.activeSessions.get(e);if(_&&c.submitMap){let h=_.promptGenerationToken;s&&h?.gen===s.gen?_.pendingSubmitMap=c.submitMap:r.warn("Interactive prompt async: stale async \u2014 token gen mismatch, skipping pendingSubmitMap write",{tokenAtEmit:s,currentToken:h,sessionId:e})}let U=i,$=c,S=!1;this.sessionKey&&(U=a.cryptoService.encryptContent(i,this.sessionKey),$={encrypted:a.cryptoService.encryptMetadata($,this.sessionKey)},S=!0);let T=this.activeSessions.get(e)?.promptGenerationToken;if(s&&T?.gen!==s.gen){r.warn("Interactive prompt emit: stale token \u2014 newer INTERACTIVE_PROMPT replaced ours; skipping AppSync emit",{sessionId:e,tokenAtEmit:s,currentToken:T});return}await this.appSyncClient.createEvent({sessionId:e,type:a.EventType.INTERACTIVE_PROMPT,source:t.source,content:U,metadata:$,promptId:t.prompt_id,timestamp:o,isEncrypted:S?!0:void 0}),r.info("Interactive prompt sent to AppSync with dynamic options",{sessionId:e})}buildQuestionWireData(e,t=[]){let i=(e.options||[]).map((c,u)=>{let l=typeof c=="string",m=l?c:c.label||"",d=l?"":c.description||"",g=l?"":c.preview||"",_={number:String(u+1),text:m};return d&&(_.description=d),g&&(_.preview=g),_});if(!e._isSubmit)for(let c of t)i.push({number:String(i.length+1),text:c.label});let n=Object.fromEntries(i.map(c=>[c.number,c.number])),s=(e.options||[]).length,o=t.findIndex(c=>c.label==="Type something"),p;if(e._isSubmit)p="Reply with 1 to submit your answers or 2 to cancel.";else if(e.multiSelect)p=`Reply with comma-separated numbers (e.g., 1,3) for "${e.header||e.question}"`;else if(o>=0){let c=String(s+o+1);p=`Reply with the number of your choice. For option ${c} (Type something), reply "${c}, your answer".`}else p="Reply with the number of your choice.";return{options:i,submitMap:n,instructions:p}}parseAskUserQuestionSynthTail(e){let t=(0,a.normalizeSnapshot)(e);if(!t)return[];let i=t.split(`
7
+ `).slice(-14),n=[],s=new Set,o=["Type something","Chat about this"];for(let p of i){let c=p.trim();for(let u of o)!s.has(u)&&c===u&&(n.push({label:u}),s.add(u))}return n}addFallbackOptions(e){e.options=[{number:"1",text:"Yes"},{number:"2",text:"Yes, and don't ask again"},{number:"3",text:"Reject and tell Claude what to do differently"}],e.submitMap={1:"1",2:"2",3:"3"},e.instructions="Reply with 1, 2, or 3. Append a message to provide alternative instructions."}buildPromptInstructions(e){return`Reply with ${e.options.map(i=>i.number).join(", ")}. Append a message to provide alternative instructions.`}parseInteractivePromptInput(e,t=3){return Se(e,t)}async markEventExecuted(e){try{await this.appSyncClient.updateEventStatus({eventId:e.eventId,sessionId:e.sessionId,timestamp:e.timestamp,deliveryStatus:a.DeliveryStatus.EXECUTED}),r.info("Event marked as EXECUTED",{eventId:e.eventId})}catch(t){r.warn("Failed to mark event as EXECUTED",{eventId:e.eventId,error:t})}}async sendPromptError(e,t){let i={error:!0},n=t,s=i,o=!1;this.sessionKey&&(n=a.cryptoService.encryptContent(t,this.sessionKey),s={encrypted:a.cryptoService.encryptMetadata(i,this.sessionKey)},o=!0),await this.appSyncClient.createEvent({sessionId:e,type:a.EventType.NOTIFICATION,source:a.EventSource.DESKTOP,content:n,metadata:s,timestamp:(0,a.prepareEventTimestamp)({orderingKey:e}),isEncrypted:o?!0:void 0})}async emitUserChoice(e,t){let i=t,n={source:"codevibe_v2_user_choice"},s=!1;this.sessionKey&&(i=a.cryptoService.encryptContent(t,this.sessionKey),n={encrypted:a.cryptoService.encryptMetadata({source:"codevibe_v2_user_choice"},this.sessionKey)},s=!0),await this.appSyncClient.createEvent({sessionId:e,type:a.EventType.ASSISTANT_RESPONSE,source:a.EventSource.DESKTOP,content:i,metadata:n,timestamp:(0,a.prepareEventTimestamp)({orderingKey:e}),isEncrypted:s?!0:void 0})}async emitAnswerAck(e,t,i){let n={promptAnswered:!0,...i},s=t,o=n,p=!1;this.sessionKey&&(s=a.cryptoService.encryptContent(t,this.sessionKey),o={encrypted:a.cryptoService.encryptMetadata(n,this.sessionKey)},p=!0),await this.appSyncClient.createEvent({sessionId:e,type:a.EventType.NOTIFICATION,source:a.EventSource.DESKTOP,content:s,metadata:o,timestamp:(0,a.prepareEventTimestamp)({orderingKey:e}),isEncrypted:p?!0:void 0})}promoteFingerprintAndClearPromptState(e){e.activeAskUserQuestionFingerprint&&(e.completedAskUserQuestionFingerprints||(e.completedAskUserQuestionFingerprints=new Set),e.completedAskUserQuestionFingerprints.add(e.activeAskUserQuestionFingerprint)),this.clearPromptState(e)}clearPromptState(e){e.waitingForPromptResponse=!1,e.pendingPromptId=void 0,e.pendingSubmitMap=void 0,e.pendingQuestionsQueue=void 0,e.pendingCurrentQuestion=void 0,e.pendingSynthTail=void 0,e.activeAskUserQuestionFingerprint=void 0,e.promptGenerationToken=void 0}async cancelV2WalkerAndExit(e,t,i,n,s){let o=t.pendingQuestionsQueue?.length??0;r.info("V2 walker mid-walker bailout \u2014 navigating to Submit/Cancel",{sessionId:e,advanceCount:o,triggerReason:i,hasFollowupText:n});let p=!1;for(let l=0;l<o;l++){let m=await this.promptResponder.sendKey(e,"Enter");if(s())return r.warn("cancelV2WalkerAndExit: aborted mid-loop (token rotated); leaving session state for new prompt",{sessionId:e,attemptedAdvances:l,totalAdvances:o,triggerReason:i}),!1;if(!m){p=!0,r.warn("cancelV2WalkerAndExit: sendKey returned false; aborting cancel sequence",{sessionId:e,attemptedAdvances:l,totalAdvances:o,triggerReason:i});break}if(await new Promise(d=>setTimeout(d,200)),s())return r.warn("cancelV2WalkerAndExit: aborted post-delay (token rotated); leaving session state",{sessionId:e,attemptedAdvances:l+1,totalAdvances:o,triggerReason:i}),!1}if(p){this.promoteFingerprintAndClearPromptState(t);try{await this.emitUserChoice(e,"AskUserQuestion bailout \u2014 desktop walker state unknown (tmux unavailable); your reply was not sent")}catch(l){r.warn("emitUserChoice on V2 cancel (sendKey failed) failed",{sessionId:e,error:l instanceof Error?l.message:String(l)})}return!1}let c=await this.promptResponder.answerInteractivePrompt(e,"2",{pressEnter:!0});if(s())return r.warn("cancelV2WalkerAndExit: aborted post-Cancel send (token rotated); leaving session state",{sessionId:e,triggerReason:i}),!1;if(!c){r.warn('cancelV2WalkerAndExit: final "2" Cancel send returned false; treating as bailout',{sessionId:e,triggerReason:i}),this.promoteFingerprintAndClearPromptState(t);try{await this.emitUserChoice(e,"AskUserQuestion bailout \u2014 Cancel keypress did not land (tmux unavailable); your reply was not sent")}catch(l){r.warn("emitUserChoice on V2 cancel (cancel send failed) failed",{sessionId:e,error:l instanceof Error?l.message:String(l)})}return!1}t.activeAskUserQuestionFingerprint&&(t.completedAskUserQuestionFingerprints||(t.completedAskUserQuestionFingerprints=new Set),t.completedAskUserQuestionFingerprints.add(t.activeAskUserQuestionFingerprint),r.info("V2 walker cancelled via mid-walker bailout \u2014 fingerprint marked closed",{sessionId:e,fingerprint:t.activeAskUserQuestionFingerprint.slice(0,16),triggerReason:i}));let u=n?"AskUserQuestion cancelled \u2014 sending your reply as a new prompt":"AskUserQuestion cancelled, no answers submitted";try{await this.emitUserChoice(e,u)}catch(l){r.warn("emitUserChoice on V2 cancel failed",{sessionId:e,error:l instanceof Error?l.message:String(l)})}return s()?(r.warn("cancelV2WalkerAndExit: aborted post-emitUserChoice (token rotated); leaving session state for new prompt",{sessionId:e,triggerReason:i}),!1):(this.clearPromptState(t),!0)}computeAskUserQuestionFingerprint(e){if(!(!e||typeof e!="object"))try{let t=this.stringifyCanonical(e);return(0,L.createHash)("sha256").update(t).digest("hex")}catch(t){r.warn("Failed to fingerprint AskUserQuestion questions",{error:t instanceof Error?t.message:String(t)});return}}stringifyCanonical(e){return e===null||typeof e!="object"?JSON.stringify(e):Array.isArray(e)?"["+e.map(i=>this.stringifyCanonical(i)).join(",")+"]":"{"+Object.keys(e).sort().map(i=>JSON.stringify(i)+":"+this.stringifyCanonical(e[i])).join(",")+"}"}async markEventExecutedIdempotent(e,t){e.processedEventIds||(e.processedEventIds=new Set),e.processedEventIds.add(t.eventId);try{await this.markEventExecuted(t)}catch(i){r.warn("[walker] markEventExecuted threw \u2014 relying on processedEventIds set for dedup",{sessionId:t.sessionId,eventId:t.eventId,error:String(i)})}}isSessionLimitExceeded(e){return this.getErrorMessage(e).includes("SESSION_LIMIT_EXCEEDED")}isUsageLimitExceeded(e){let t=this.getErrorMessage(e);return t.includes("MESSAGE_LIMIT_EXCEEDED")||t.includes("IMAGE_LIMIT_EXCEEDED")}getErrorMessage(e){if(e instanceof Error)return e.message;if(typeof e=="object"&&e!==null){let t=e;if(t.errors&&Array.isArray(t.errors))return t.errors.map(i=>i.message||"").join(" ");if(typeof t.message=="string")return t.message}return String(e)}displaySubscriptionLimitError(e,t){let i=this.getErrorMessage(e),n="",s=i.match(/for your (\w+) plan/i);s&&(n=` (${s[1]} tier)`);let o="",p=i.match(/of (\d+)/);switch(p&&(o=` [Limit: ${p[1]}]`),console.log(`
8
8
  `+"=".repeat(60)),console.log("\u26A0\uFE0F SUBSCRIPTION LIMIT REACHED"),console.log("=".repeat(60)),t){case"session":console.log(`You have reached the maximum number of active sessions${n}.`),console.log(`${o}`),console.log(`
9
9
  To continue, please:`),console.log(" \u2022 Close an existing Claude Code session, or"),console.log(" \u2022 Upgrade your subscription in the CodeVibe iOS app");break;case"message":console.log(`You have reached your monthly message limit${n}.`),console.log(`${o}`),console.log(`
10
10
  To continue, please:`),console.log(" \u2022 Wait until your usage resets next month, or"),console.log(" \u2022 Upgrade your subscription in the CodeVibe iOS app");break;case"image":console.log(`You have reached your monthly image attachment limit${n}.`),console.log(`${o}`),console.log(`
11
11
  To continue, please:`),console.log(" \u2022 Wait until your usage resets next month, or"),console.log(" \u2022 Upgrade your subscription in the CodeVibe iOS app");break}console.log(`
12
12
  Note: You can still use Claude Code normally from your desktop.`),console.log("This limit only affects syncing with the mobile app."),console.log("=".repeat(60)+`
13
- `),r.error("Subscription limit exceeded",{limitType:t,errorMessage:i})}async downloadAttachment(e,t,i){try{let n=e.isEncrypted??i??!1;r.info("Downloading attachment - START",{id:e.id,type:e.type,filename:e.filename,s3Key:e.s3Key,attachmentIsEncrypted:e.isEncrypted,eventIsEncrypted:i,shouldDecrypt:n,hasSessionKey:!!this.sessionKey});let{downloadUrl:s}=await this.appSyncClient.getAttachmentDownloadUrl(e.s3Key),o=await fetch(s);if(!o.ok)throw new Error(`Failed to download attachment: ${o.status} ${o.statusText}`);let a=Buffer.from(await o.arrayBuffer());if(r.info("Attachment downloaded",{id:e.id,downloadedSize:a.length,first20Bytes:a.slice(0,20).toString("hex")}),r.info("Checking decryption conditions",{id:e.id,shouldDecrypt:n,hasSessionKey:!!this.sessionKey,willDecrypt:!!(n&&this.sessionKey)}),n&&this.sessionKey)try{r.info("Decrypting attachment",{id:e.id,encryptedSize:a.length}),a=p.cryptoService.decryptData(a,this.sessionKey),r.info("Attachment decrypted successfully",{id:e.id,decryptedSize:a.length,first20Bytes:a.slice(0,20).toString("hex")})}catch(m){throw r.error("Failed to decrypt attachment:",{id:e.id,error:m}),new Error("Failed to decrypt attachment")}else n&&!this.sessionKey?r.warn("Cannot decrypt attachment - no session key available",{id:e.id}):r.info("Skipping decryption - attachment not encrypted or no session key",{id:e.id,shouldDecrypt:n,hasSessionKey:!!this.sessionKey});let l=O.join(Y.tmpdir(),"codevibe-claude",t);_.existsSync(l)||_.mkdirSync(l,{recursive:!0});let u="",d=e.filename;if(n&&e.filename&&this.sessionKey)try{d=p.cryptoService.decryptContent(e.filename,this.sessionKey)}catch{d=e.filename}if(d){let m=O.extname(d);m&&(u=m)}u||(u={"image/jpeg":".jpg","image/png":".png","image/gif":".gif","image/webp":".webp","image/heic":".heic","application/pdf":".pdf"}[e.type]||".bin");let g=`attachment-${e.id}${u}`,c=O.join(l,g);return _.writeFileSync(c,a),r.info("Attachment saved to temp file",{id:e.id,filePath:c,size:a.length,wasDecrypted:n&&!!this.sessionKey}),c}catch(n){return r.error("Failed to download attachment:",{id:e.id,error:n}),null}}async executeMobilePrompt(e,t){let i=t.content||"",n=t.attachments||[];r.info("Executing mobile prompt via tmux",{sessionId:e,promptLength:i.length,attachmentCount:n.length});let s=[];if(n.length>0){r.info("Downloading attachments for prompt",{count:n.length});for(let o of n){let a=await this.downloadAttachment(o,e,t.isEncrypted);a&&s.push(a)}if(s.length>0){let o=s.map(a=>`[Attached file: ${a}]`).join(`
13
+ `),r.error("Subscription limit exceeded",{limitType:t,errorMessage:i})}async downloadAttachment(e,t,i){try{let n=e.isEncrypted??i??!1;r.info("Downloading attachment - START",{id:e.id,type:e.type,filename:e.filename,s3Key:e.s3Key,attachmentIsEncrypted:e.isEncrypted,eventIsEncrypted:i,shouldDecrypt:n,hasSessionKey:!!this.sessionKey});let{downloadUrl:s}=await this.appSyncClient.getAttachmentDownloadUrl(e.s3Key),o=await fetch(s);if(!o.ok)throw new Error(`Failed to download attachment: ${o.status} ${o.statusText}`);let p=Buffer.from(await o.arrayBuffer());if(r.info("Attachment downloaded",{id:e.id,downloadedSize:p.length,first20Bytes:p.slice(0,20).toString("hex")}),r.info("Checking decryption conditions",{id:e.id,shouldDecrypt:n,hasSessionKey:!!this.sessionKey,willDecrypt:!!(n&&this.sessionKey)}),n&&this.sessionKey)try{r.info("Decrypting attachment",{id:e.id,encryptedSize:p.length}),p=a.cryptoService.decryptData(p,this.sessionKey),r.info("Attachment decrypted successfully",{id:e.id,decryptedSize:p.length,first20Bytes:p.slice(0,20).toString("hex")})}catch(g){throw r.error("Failed to decrypt attachment:",{id:e.id,error:g}),new Error("Failed to decrypt attachment")}else n&&!this.sessionKey?r.warn("Cannot decrypt attachment - no session key available",{id:e.id}):r.info("Skipping decryption - attachment not encrypted or no session key",{id:e.id,shouldDecrypt:n,hasSessionKey:!!this.sessionKey});let c=N.join(Y.tmpdir(),"codevibe-claude",t);M.existsSync(c)||M.mkdirSync(c,{recursive:!0});let u="",l=e.filename;if(n&&e.filename&&this.sessionKey)try{l=a.cryptoService.decryptContent(e.filename,this.sessionKey)}catch{l=e.filename}if(l){let g=N.extname(l);g&&(u=g)}u||(u={"image/jpeg":".jpg","image/png":".png","image/gif":".gif","image/webp":".webp","image/heic":".heic","application/pdf":".pdf"}[e.type]||".bin");let m=`attachment-${e.id}${u}`,d=N.join(c,m);return M.writeFileSync(d,p),r.info("Attachment saved to temp file",{id:e.id,filePath:d,size:p.length,wasDecrypted:n&&!!this.sessionKey}),d}catch(n){return r.error("Failed to download attachment:",{id:e.id,error:n}),null}}async executeMobilePrompt(e,t){let i=t.content||"",n=t.attachments||[];r.info("Executing mobile prompt via tmux",{sessionId:e,promptLength:i.length,attachmentCount:n.length});let s=[];if(n.length>0){r.info("Downloading attachments for prompt",{count:n.length});for(let o of n){let p=await this.downloadAttachment(o,e,t.isEncrypted);p&&s.push(p)}if(s.length>0){let o=s.map(p=>`[Attached file: ${p}]`).join(`
14
14
  `);i?i=`${o}
15
15
 
16
16
  ${i}`:i=`${o}
17
17
 
18
- Please analyze the attached file(s).`,r.info("Prompt updated with attachment paths",{attachmentCount:s.length,newPromptLength:i.length})}}this.trackMobilePrompt(e,i);try{if(await this.promptResponder.answerInteractivePrompt(e,i)){try{await this.appSyncClient.updateEventStatus({eventId:t.eventId,sessionId:t.sessionId,timestamp:t.timestamp,deliveryStatus:p.DeliveryStatus.EXECUTED}),r.info("Event marked as EXECUTED",{eventId:t.eventId})}catch(l){r.warn("Failed to mark event as EXECUTED",{eventId:t.eventId,error:l})}r.info("Mobile prompt sent successfully",{sessionId:e});let a=s.length>0?`Prompt with ${s.length} attachment(s) sent to Claude Code`:`Prompt "${i.substring(0,50)}${i.length>50?"...":""}" sent to Claude Code`;await this.appSyncClient.createEvent({sessionId:e,type:p.EventType.NOTIFICATION,source:p.EventSource.DESKTOP,content:a,metadata:{mobilePrompt:!0,attachmentCount:s.length}})}else r.error("Failed to send mobile prompt",{sessionId:e}),await this.appSyncClient.createEvent({sessionId:e,type:p.EventType.NOTIFICATION,source:p.EventSource.DESKTOP,content:"Failed to send prompt to Claude Code",metadata:{error:!0}})}catch(o){r.error("Failed to execute mobile prompt:",o)}}};async function Me(){let f=process.argv[2]||process.env.CLAUDE_SESSION_ID;f?r.info(`Starting MCP server for session: ${f}`):r.info("Starting MCP server without initial session ID (will be set on SessionStart)");let e=new J(f);try{await e.start();let t=e.getPort();console.log(`PORT=${t}`);let i=!1,n=async s=>{if(i){r.info("Shutdown already in progress, ignoring additional signal");return}i=!0,r.info(`Received ${s} signal, stopping server...`);try{await e.stop(),r.info("Graceful shutdown completed"),process.exit(0)}catch(o){r.error("Error during shutdown:",o),process.exit(1)}};process.on("SIGINT",()=>n("SIGINT")),process.on("SIGTERM",()=>n("SIGTERM")),process.on("SIGHUP",()=>n("SIGHUP")),process.on("uncaughtException",async s=>{r.error("Uncaught exception:",s),await n("uncaughtException")}),process.on("unhandledRejection",async s=>{r.error("Unhandled rejection:",s),await n("unhandledRejection")})}catch(t){r.error("Failed to start MCP Server:",t),process.exit(1)}}function Ee(f,e=3){let t=f.trim(),i=t.match(/^(\d+)$/);if(i){let s=parseInt(i[1]);if(s>=1&&s<=e)return{action:"select_option",option:i[1]}}let n=t.match(/^(\d+)[,.:;\-\s\n]+(.+)$/s);if(n){let s=parseInt(n[1]);if(s>=1&&s<=e)return{action:"option_with_followup",option:n[1],followUpText:n[2].trim()}}return{action:"send_as_response"}}process.env.JEST_WORKER_ID||Me().catch(f=>{r.error("Unhandled error in main:",f),process.exit(1)});0&&(module.exports={McpServer,parseInteractivePromptInput});
18
+ Please analyze the attached file(s).`,r.info("Prompt updated with attachment paths",{attachmentCount:s.length,newPromptLength:i.length})}}this.trackMobilePrompt(e,i);try{if(await this.promptResponder.answerInteractivePrompt(e,i)){try{await this.appSyncClient.updateEventStatus({eventId:t.eventId,sessionId:t.sessionId,timestamp:t.timestamp,deliveryStatus:a.DeliveryStatus.EXECUTED}),r.info("Event marked as EXECUTED",{eventId:t.eventId})}catch(c){r.warn("Failed to mark event as EXECUTED",{eventId:t.eventId,error:c})}r.info("Mobile prompt sent successfully",{sessionId:e});let p=s.length>0?`Prompt with ${s.length} attachment(s) sent to Claude Code`:`Prompt "${i.substring(0,50)}${i.length>50?"...":""}" sent to Claude Code`;await this.appSyncClient.createEvent({sessionId:e,type:a.EventType.NOTIFICATION,source:a.EventSource.DESKTOP,content:p,metadata:{mobilePrompt:!0,attachmentCount:s.length},timestamp:(0,a.prepareEventTimestamp)({orderingKey:e})})}else r.error("Failed to send mobile prompt",{sessionId:e}),await this.appSyncClient.createEvent({sessionId:e,type:a.EventType.NOTIFICATION,source:a.EventSource.DESKTOP,content:"Failed to send prompt to Claude Code",metadata:{error:!0},timestamp:(0,a.prepareEventTimestamp)({orderingKey:e})})}catch(o){r.error("Failed to execute mobile prompt:",o)}}};async function Me(){let f=process.argv[2]||process.env.CLAUDE_SESSION_ID;f?r.info(`Starting MCP server for session: ${f}`):r.info("Starting MCP server without initial session ID (will be set on SessionStart)");let e=new J(f);try{await e.start();let t=e.getPort();console.log(`PORT=${t}`);let i=!1,n=async s=>{if(i){r.info("Shutdown already in progress, ignoring additional signal");return}i=!0,r.info(`Received ${s} signal, stopping server...`);try{await e.stop(),r.info("Graceful shutdown completed"),process.exit(0)}catch(o){r.error("Error during shutdown:",o),process.exit(1)}};process.on("SIGINT",()=>n("SIGINT")),process.on("SIGTERM",()=>n("SIGTERM")),process.on("SIGHUP",()=>n("SIGHUP")),process.on("uncaughtException",async s=>{r.error("Uncaught exception:",s),await n("uncaughtException")}),process.on("unhandledRejection",async s=>{r.error("Unhandled rejection:",s),await n("unhandledRejection")})}catch(t){r.error("Failed to start MCP Server:",t),process.exit(1)}}function Se(f,e=3){let t=f.trim(),i=t.match(/^(\d+)$/);if(i){let s=parseInt(i[1]);if(s>=1&&s<=e)return{action:"select_option",option:i[1]}}let n=t.match(/^(\d+)[,.:;\-\s\n]+(.+)$/s);if(n){let s=parseInt(n[1]);if(s>=1&&s<=e)return{action:"option_with_followup",option:n[1],followUpText:n[2].trim()}}return{action:"send_as_response"}}process.env.JEST_WORKER_ID||Me().catch(f=>{r.error("Unhandled error in main:",f),process.exit(1)});0&&(module.exports={McpServer,parseInteractivePromptInput});
@@ -11,6 +11,6 @@ export type { Config, Environment } from './config';
11
11
  export { Logger, logger, createLogger } from './logger';
12
12
  export { parseInteractivePrompt, normalizeSnapshot, } from './prompt-parser';
13
13
  export type { ParsedInteractivePrompt, PromptKind, InteractivePromptOption, } from './prompt-parser';
14
- export { resumeOrCreateSession, prepareSessionEncryption, rekeySessionForNewDevices, startDeviceKeyWatcher, registerDeviceEncryptionKey, } from './session';
14
+ export { resumeOrCreateSession, prepareSessionEncryption, rekeySessionForNewDevices, startDeviceKeyWatcher, registerDeviceEncryptionKey, prepareEventTimestamp, _resetPrepareEventTimestampForTesting, } from './session';
15
15
  export type { ResumeOrCreateSessionInput, ResumeOrCreateSessionResult } from './session';
16
16
  export * from './types';
@@ -1,9 +1,9 @@
1
- "use strict";var It=Object.create;var ce=Object.defineProperty;var At=Object.getOwnPropertyDescriptor;var Tt=Object.getOwnPropertyNames;var Ct=Object.getPrototypeOf,xt=Object.prototype.hasOwnProperty;var x=(n,e)=>()=>(n&&(e=n(n=0)),e);var Me=(n,e)=>{for(var t in e)ce(n,t,{get:e[t],enumerable:!0})},Be=(n,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of Tt(e))!xt.call(n,i)&&i!==t&&ce(n,i,{get:()=>e[i],enumerable:!(r=At(e,i))||r.enumerable});return n};var v=(n,e,t)=>(t=n!=null?It(Ct(n)):{},Be(e||!n||!n.__esModule?ce(t,"default",{value:n,enumerable:!0}):t,n)),Dt=n=>Be(ce({},"__esModule",{value:!0}),n);function _t(n,e){if(e instanceof Error){let t={name:e.name,message:e.message};e.stack&&(t.stack=e.stack);for(let r of Object.keys(e))r in t||(t[r]=e[r]);return t}return e}function ve(n){return new W(n)}var G,de,qe,Fe,W,c,He=x(()=>{"use strict";G=v(require("fs")),de=v(require("path")),qe=v(require("os")),Fe={debug:0,info:1,warn:2,error:3};W=class{constructor(e){this.name=e.name,this.logFile=e.logFile,this.level=e.level||"info",this.enableConsole=e.console??!1,this.logFile&&this.ensureLogDir()}ensureLogDir(){if(this.logFile){let e=de.dirname(this.logFile);G.existsSync(e)||G.mkdirSync(e,{recursive:!0})}}shouldLog(e){return Fe[e]>=Fe[this.level]}formatMessage(e,t,r){let i=new Date().toISOString(),s=e.toUpperCase().padEnd(5),o=`[${i}] [${s}] [${this.name}] ${t}`;return r!==void 0&&(r instanceof Error?(o+=` ${r.name}: ${r.message}`,r.stack&&(o+=`
2
- ${r.stack}`)):typeof r=="object"?o+=` ${JSON.stringify(r,_t)}`:o+=` ${r}`),o}log(e,t,r){if(!this.shouldLog(e))return;let i=this.formatMessage(e,t,r);if(this.logFile)try{G.appendFileSync(this.logFile,i+`
3
- `)}catch{}if(this.enableConsole)switch(e){case"error":console.error(i);break;case"warn":console.warn(i);break;default:console.log(i)}}debug(e,t){this.log("debug",e,t)}info(e,t){this.log("info",e,t)}warn(e,t){this.log("warn",e,t)}error(e,t){this.log("error",e,t)}setLevel(e){this.level=e}};c=new W({name:"codevibe-core",logFile:de.join(qe.tmpdir(),"codevibe-core.log"),level:"info"})});var M=x(()=>{"use strict";He()});function Kt(n){for(let e of n)try{process.stderr.write(e+`
4
- `)}catch{}}function Rt(){Se=ke.join(Ve.homedir(),".codevibe");try{N.mkdirSync(Se,{recursive:!0,mode:448})}catch{}B="file"}function Pt(){if(B!==null||we!==null)return;let optedIn=process.env.CODEVIBE_ALLOW_FILE_KEYCHAIN==="1";if(optedIn){Kt(["","\u26A0 CodeVibe: file-based credential storage selected (CODEVIBE_ALLOW_FILE_KEYCHAIN=1).","\u26A0 Location: ~/.codevibe/ (directory 0700, files 0600)","\u26A0 Trust level: equivalent to ~/.ssh/id_rsa \u2014 weaker than OS keyring.","\u26A0 To use the OS keyring instead, unset CODEVIBE_ALLOW_FILE_KEYCHAIN and","\u26A0 install libsecret-1-0 + a running keyring daemon (Linux) or use the","\u26A0 native Keychain (macOS) / Credential Manager (Windows).",""]),c.warn("[keychain-backend] Using file-based storage at ~/.codevibe (CODEVIBE_ALLOW_FILE_KEYCHAIN=1 explicit opt-in)"),Rt();return}let keytarLoadError=null;try{let nodeRequire=eval("require");D=nodeRequire("keytar")}catch(n){keytarLoadError=n instanceof Error?n.message:String(n),D=null}if(D){B="keytar",c.info("[keychain-backend] Using keytar (OS-native keyring)");return}we=new le(["CodeVibe could not load the OS-native keyring (keytar).",`Reason: ${keytarLoadError??"unknown"}`,"","Options to fix this:"," 1. (Linux) Install libsecret and a keyring daemon:"," sudo apt install libsecret-1-0 gnome-keyring"," Then unlock the keyring for your user session.",""," 2. (Headless / CI / Docker) Opt in to file-based credential"," storage at ~/.codevibe/ (0600 files). This is equivalent"," in trust to ~/.ssh/id_rsa \u2014 not the OS keyring:"," export CODEVIBE_ALLOW_FILE_KEYCHAIN=1"].join(`
5
- `))}function Ot(n){return n.replace(/[^a-zA-Z0-9._-]/g,"_")}function Je(n){return ke.join(Se,`${Ot(n)}.json`)}function be(n){try{let e=N.readFileSync(Je(n),"utf-8"),t=JSON.parse(e);return t&&typeof t=="object"?t:{}}catch{return{}}}function je(n,e){let t=Je(n);N.writeFileSync(t,JSON.stringify(e,null,2),{mode:384});try{N.chmodSync(t,384)}catch{}}function Ee(){if(Pt(),B===null)throw we??new le("Keychain backend not initialized")}async function Ie(n,e){return Ee(),B==="keytar"&&D?D.getPassword(n,e):be(n)[e]??null}async function Ae(n,e,t){if(Ee(),B==="keytar"&&D){await D.setPassword(n,e,t);return}let r=be(n);r[e]=t,je(n,r)}async function Te(n,e){if(Ee(),B==="keytar"&&D)return D.deletePassword(n,e);let t=be(n);return e in t?(delete t[e],je(n,t),!0):!1}var Ve,ke,N,le,B,D,Se,we,Ge=x(()=>{"use strict";Ve=v(require("os")),ke=v(require("path")),N=v(require("fs"));M();le=class extends Error{constructor(e){super(e),this.name="KeychainBackendUnavailableError"}},B=null,D=null,Se="",we=null});var T,_,Ce,Ut,z,E,ze=x(()=>{"use strict";T=v(require("crypto")),_=class extends Error{constructor(e){super(e),this.name="CryptoError"}},Ce=1,Ut="CodeVibe E2E v1",z=class n{constructor(){}static getInstance(){return n.instance||(n.instance=new n),n.instance}generateKeyPair(){let e=T.createECDH("prime256v1");e.generateKeys();let r=e.getPublicKey().subarray(1).toString("base64");return{privateKey:e.getPrivateKey().toString("base64"),publicKey:r}}generateSessionKey(){return T.randomBytes(32).toString("base64")}deriveSharedKey(e,t){try{let r=T.createECDH("prime256v1"),i=Buffer.from(e,"base64");r.setPrivateKey(i);let s=Buffer.from(t,"base64"),o=s.length===65&&s[0]===4?s:Buffer.concat([Buffer.from([4]),s]),a=r.computeSecret(o),d=T.hkdfSync("sha256",a,Buffer.alloc(0),Buffer.from(Ut,"utf8"),32);return Buffer.from(d)}catch(r){throw new _(`Failed to derive shared key: ${r}`)}}encryptSessionKey(e,t){let r=this.generateKeyPair(),i=this.deriveSharedKey(r.privateKey,t),s=Buffer.from(e,"base64");return{encryptedKey:this.encrypt(s,i).toString("base64"),ephemeralPublicKey:r.publicKey}}decryptSessionKey(e,t){let r=this.deriveSharedKey(t,e.ephemeralPublicKey),i=Buffer.from(e.encryptedKey,"base64");return this.decrypt(i,r).toString("base64")}encryptContent(e,t){let r=Buffer.from(t,"base64"),i=Buffer.from(e,"utf8");return this.encrypt(i,r).toString("base64")}decryptContent(e,t){let r=Buffer.from(t,"base64"),i=Buffer.from(e,"base64");return this.decrypt(i,r).toString("utf8")}encryptMetadata(e,t){let r=JSON.stringify(e);return this.encryptContent(r,t)}decryptMetadata(e,t){let r=this.decryptContent(e,t);return JSON.parse(r)}encryptData(e,t){let r=Buffer.from(t,"base64");return this.encrypt(e,r)}decryptData(e,t){let r=Buffer.from(t,"base64");return this.decrypt(e,r)}encrypt(e,t){let r=T.randomBytes(12),i=T.createCipheriv("aes-256-gcm",t,r),s=Buffer.concat([i.update(e),i.final()]),o=i.getAuthTag();return Buffer.concat([r,s,o])}decrypt(e,t){let r=e.subarray(0,12),i=e.subarray(e.length-16),s=e.subarray(12,e.length-16),o=T.createDecipheriv("aes-256-gcm",t,r);o.setAuthTag(i);try{return Buffer.concat([o.update(s),o.final()])}catch{throw new _("Decryption failed: Invalid ciphertext or authentication tag")}}serializePrivateKey(e){return e}deserializePrivateKey(e){return e}},E=z.getInstance()});var ee=x(()=>{"use strict";ze()});function I(){let n=process.env.ENVIRONMENT;return n==="development"||n==="production"?n:"production"}function ue(n){let e=n||I();return pe={...F[e],aws:{...F[e].aws,region:process.env.AWS_REGION||F[e].aws.region,appsyncUrl:process.env.APPSYNC_URL||F[e].aws.appsyncUrl,cognitoUserPoolId:process.env.COGNITO_USER_POOL_ID||F[e].aws.cognitoUserPoolId,cognitoClientId:process.env.COGNITO_CLIENT_ID||F[e].aws.cognitoClientId,cognitoDomain:process.env.COGNITO_DOMAIN||F[e].aws.cognitoDomain}},Ye=!0,pe}function w(){return(!Ye||!pe)&&ue(),pe}var te,re,F,pe,Ye,Xe=x(()=>{"use strict";te=v(require("os")),re=v(require("path")),F={development:{environment:"development",aws:{region:"us-east-1",appsyncUrl:"https://api-dev.codevibe.quantiya.ai/graphql",cognitoUserPoolId:"us-east-1_yVwWDPvvJ",cognitoClientId:"e9r5apv6v5uui3l928r2ris0r",cognitoDomain:"codevibe-development.auth.us-east-1.amazoncognito.com"},keychain:{serviceName:"ai.quantiya.app.codevibe"},server:{port:3456,host:"127.0.0.1",dynamicPort:!0},claude:{command:"claude",defaultTimeout:6e4},codex:{command:"codex",defaultTimeout:6e4,sessionsDir:re.default.join(te.default.homedir(),".codex","sessions"),approvalTimeoutMs:5e3},gemini:{command:"gemini",defaultTimeout:6e4,transcriptDir:re.default.join(te.default.homedir(),".gemini","tmp")}},production:{environment:"production",aws:{region:"us-east-1",appsyncUrl:"https://api.codevibe.quantiya.ai/graphql",cognitoUserPoolId:"us-east-1_mNRO0j5og",cognitoClientId:"5p04dbc9ojptc5r8n7605fg78f",cognitoDomain:"codevibe-production.auth.us-east-1.amazoncognito.com"},keychain:{serviceName:"ai.quantiya.app.codevibe"},server:{port:3456,host:"127.0.0.1",dynamicPort:!0},claude:{command:"claude",defaultTimeout:6e4},codex:{command:"codex",defaultTimeout:6e4,sessionsDir:re.default.join(te.default.homedir(),".codex","sessions"),approvalTimeoutMs:5e3},gemini:{command:"gemini",defaultTimeout:6e4,transcriptDir:re.default.join(te.default.homedir(),".gemini","tmp")}}},pe=null,Ye=!1});var Y=x(()=>{"use strict";Xe()});var ye,Ze,K,xe,$t,q,y,Qe=x(()=>{"use strict";ye=v(require("os")),Ze=require("uuid");Ge();ee();Y();M();K=class extends Error{constructor(e){super(e),this.name="KeychainError"}},xe="device-identity",$t="tokens-",q=class n{constructor(){this.deviceIdentity=null;this.sessionKeyCache=new Map;this.isRegistered=!1;this._serviceName=null}get serviceName(){return this._serviceName||(this._serviceName=w().keychain.serviceName),this._serviceName}static getInstance(){return n.instance||(n.instance=new n),n.instance}async getDeviceIdentity(){if(this.deviceIdentity)return this.deviceIdentity;let e=await Ie(this.serviceName,xe);return e?(this.deviceIdentity=JSON.parse(e),c.info(`[KeychainManager] Loaded device identity: ${this.deviceIdentity.deviceId}`),this.deviceIdentity):null}async setDeviceIdentity(e){try{await Ae(this.serviceName,xe,JSON.stringify(e)),this.deviceIdentity=e,c.info(`[KeychainManager] Saved device identity: ${e.deviceId}`)}catch(t){throw c.error(`[KeychainManager] Failed to save device identity: ${t}`),new K(`Failed to save device identity: ${t}`)}}async getOrCreateDeviceIdentity(){let e=await this.getDeviceIdentity();if(e)return e;let t=E.generateKeyPair();return e={deviceId:(0,Ze.v4)().toUpperCase(),privateKey:t.privateKey,publicKey:t.publicKey,createdAt:new Date().toISOString()},await this.setDeviceIdentity(e),c.info(`[KeychainManager] Generated new device identity: ${e.deviceId}`),e}async getDeviceId(){return(await this.getOrCreateDeviceIdentity()).deviceId}async getDevicePublicKey(){return(await this.getOrCreateDeviceIdentity()).publicKey}async getDevicePrivateKey(){return(await this.getOrCreateDeviceIdentity()).privateKey}async hasDeviceIdentity(){return await this.getDeviceIdentity()!==null}async deleteDeviceIdentity(){try{await Te(this.serviceName,xe),this.deviceIdentity=null,this.sessionKeyCache.clear(),this.isRegistered=!1,c.info("[KeychainManager] Deleted device identity")}catch(e){throw c.error(`[KeychainManager] Failed to delete device identity: ${e}`),new K(`Failed to delete device identity: ${e}`)}}getTokenAccount(e){return`${$t}${e}`}async getTokens(e="production"){let t=await Ie(this.serviceName,this.getTokenAccount(e));if(!t)return null;let r=JSON.parse(t);return c.debug(`[KeychainManager] Loaded tokens for ${e}`),r}async setTokens(e,t="production"){try{await Ae(this.serviceName,this.getTokenAccount(t),JSON.stringify(e)),c.info(`[KeychainManager] Saved tokens for ${t}`,{userId:e.userId,email:e.email})}catch(r){throw c.error(`[KeychainManager] Failed to save tokens: ${r}`),new K(`Failed to save tokens: ${r}`)}}async deleteTokens(e="production"){try{let t=await Te(this.serviceName,this.getTokenAccount(e));return t&&c.info(`[KeychainManager] Deleted tokens for ${e}`),t}catch(t){return c.error(`[KeychainManager] Failed to delete tokens: ${t}`),!1}}isTokenExpired(e){return Date.now()>=e.expiresAt-3e5}async getSessionKey(e,t){let r=this.sessionKeyCache.get(e);if(r)return r;if(!t||t.length===0)return null;let i=await this.getDeviceId(),s=t.find(d=>d.deviceId===i);if(!s)return c.warn(`[KeychainManager] Device ${i} not found in encryptedKeys`),null;let o=await this.getDevicePrivateKey(),a=E.decryptSessionKey(s,o);return this.sessionKeyCache.set(e,a),c.info(`[KeychainManager] Decrypted and cached session key for ${e}`),a}createSessionKey(e,t){let r=E.generateSessionKey(),i=[],s=[];for(let o of e)try{let a=E.encryptSessionKey(r,o.publicKey);i.push({deviceId:o.deviceId,encryptedKey:a.encryptedKey,ephemeralPublicKey:a.ephemeralPublicKey})}catch(a){c.warn("[KeychainManager] Skipping device with invalid public key",{deviceId:o.deviceId,error:a instanceof Error?a.message:String(a)}),s.push(o.deviceId);try{t?.onDeviceSkipped?.(s.length)}catch{}}if(i.length===0)throw new _(`Failed to encrypt session key for any of ${e.length} devices`);return c.info("[KeychainManager] Created session key",{encryptedCount:i.length,skippedCount:s.length,totalCount:e.length}),{sessionKey:r,encryptedKeys:i,skippedDeviceIds:s}}cacheSessionKey(e,t){this.sessionKeyCache.set(e,t)}getCachedSessionKey(e){return this.sessionKeyCache.get(e)??null}getCachedSessionIds(){return Array.from(this.sessionKeyCache.keys())}clearSessionKey(e){this.sessionKeyCache.delete(e)}clearAllSessionKeys(){this.sessionKeyCache.clear()}getIsRegistered(){return this.isRegistered}setIsRegistered(e){this.isRegistered=e}getDeviceName(){return ye.hostname()||"CLI Client"}getDevicePlatform(){let e=ye.platform();return e==="darwin"?"MACOS":e==="linux"?"LINUX":e==="win32"?"WINDOWS":"CLI"}async clearAllData(){await this.deleteDeviceIdentity(),await this.deleteTokens("development"),await this.deleteTokens("production"),this.sessionKeyCache.clear(),this.isRegistered=!1,c.info("[KeychainManager] Cleared all data")}},y=q.getInstance()});var et={};Me(et,{KeychainError:()=>K,KeychainManager:()=>q,keychainManager:()=>y});var R=x(()=>{"use strict";Qe()});var sr={};Me(sr,{AgentType:()=>st,AppSyncClient:()=>ne,AuthService:()=>Q,CryptoError:()=>_,CryptoService:()=>z,DeliveryStatus:()=>it,ENCRYPTION_VERSION:()=>Ce,EventSource:()=>De,EventType:()=>nt,KeychainError:()=>K,KeychainManager:()=>q,Logger:()=>W,SessionStatus:()=>he,authService:()=>P,createLogger:()=>ve,cryptoService:()=>E,errorWasBeaconed:()=>se,fireAuthCompletedBeacon:()=>ie,fireAuthFailedBeacon:()=>b,getConfig:()=>w,getEnvironment:()=>I,getErrorReason:()=>Re,keychainManager:()=>y,loadConfig:()=>ue,logger:()=>c,markErrorBeaconed:()=>A,mutations:()=>C,normalizeSnapshot:()=>$e,parseInteractivePrompt:()=>St,prepareSessionEncryption:()=>fe,queries:()=>U,registerDeviceEncryptionKey:()=>ae,rekeySessionForNewDevices:()=>j,resumeOrCreateSession:()=>Le,runAuthCli:()=>me,startDeviceKeyWatcher:()=>We,subscriptions:()=>H});module.exports=Dt(sr);R();ee();var V=v(require("ws")),J=require("uuid");Y();M();R();var tt=v(require("dns")),rt=v(require("fs"));if(Lt())try{tt.setDefaultResultOrder("ipv4first")}catch{}function Lt(){if(process.platform!=="linux")return!1;try{let n=rt.readFileSync("/proc/sys/kernel/osrelease","utf8");return/microsoft|wsl/i.test(n)}catch{return!1}}async function X(n,e,t){try{return await fetch(n,e)}catch(r){let i=r?.cause?.code,s=r?.cause?.message,o=i||s||r?.message||"unknown",a=Wt(i),d=t?`${t}: `:"",l=`Node ${process.version} on ${process.platform}`,h=[`${d}Cannot reach ${n}`,` Underlying error: ${o}`];a&&h.push(` Suggested fix: ${a}`),h.push(` Platform: ${l}`);let p=new Error(h.join(`
6
- `));throw p.cause=r,p}}function Wt(n){if(!n)return null;switch(n){case"ENOTFOUND":case"EAI_AGAIN":return'DNS resolution failed. On WSL Ubuntu, check /etc/resolv.conf, or try running with NODE_OPTIONS="--dns-result-order=ipv4first".';case"ETIMEDOUT":case"ECONNREFUSED":case"ECONNRESET":case"EHOSTUNREACH":case"ENETUNREACH":return`Network unreachable. On WSL Ubuntu, try NODE_OPTIONS="--dns-result-order=ipv4first" (WSL's IPv6 is often broken). If behind a corporate proxy, set HTTPS_PROXY.`;case"CERT_HAS_EXPIRED":case"CERT_NOT_YET_VALID":return"TLS certificate time error \u2014 likely system clock drift. On WSL, run `sudo hwclock -s`, or shut down WSL from PowerShell with `wsl --shutdown` and restart.";case"UNABLE_TO_GET_ISSUER_CERT_LOCALLY":case"SELF_SIGNED_CERT_IN_CHAIN":case"UNABLE_TO_VERIFY_LEAF_SIGNATURE":case"DEPTH_ZERO_SELF_SIGNED_CERT":return"Corporate HTTPS proxy detected \u2014 the TLS cert is not trusted by Node. Set NODE_EXTRA_CA_CERTS=/path/to/corporate-ca.pem, or configure HTTPS_PROXY if a proxy is required.";default:return null}}var U={getSession:`
1
+ "use strict";var Ct=Object.create;var de=Object.defineProperty;var xt=Object.getOwnPropertyDescriptor;var Dt=Object.getOwnPropertyNames;var _t=Object.getPrototypeOf,Kt=Object.prototype.hasOwnProperty;var x=(n,e)=>()=>(n&&(e=n(n=0)),e);var qe=(n,e)=>{for(var t in e)de(n,t,{get:e[t],enumerable:!0})},He=(n,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of Dt(e))!Kt.call(n,i)&&i!==t&&de(n,i,{get:()=>e[i],enumerable:!(r=xt(e,i))||r.enumerable});return n};var v=(n,e,t)=>(t=n!=null?Ct(_t(n)):{},He(e||!n||!n.__esModule?de(t,"default",{value:n,enumerable:!0}):t,n)),Rt=n=>He(de({},"__esModule",{value:!0}),n);function Pt(n,e){if(e instanceof Error){let t={name:e.name,message:e.message};e.stack&&(t.stack=e.stack);for(let r of Object.keys(e))r in t||(t[r]=e[r]);return t}return e}function Se(n){return new W(n)}var z,le,Ge,Ve,W,c,Je=x(()=>{"use strict";z=v(require("fs")),le=v(require("path")),Ge=v(require("os")),Ve={debug:0,info:1,warn:2,error:3};W=class{constructor(e){this.name=e.name,this.logFile=e.logFile,this.level=e.level||"info",this.enableConsole=e.console??!1,this.logFile&&this.ensureLogDir()}ensureLogDir(){if(this.logFile){let e=le.dirname(this.logFile);z.existsSync(e)||z.mkdirSync(e,{recursive:!0})}}shouldLog(e){return Ve[e]>=Ve[this.level]}formatMessage(e,t,r){let i=new Date().toISOString(),s=e.toUpperCase().padEnd(5),o=`[${i}] [${s}] [${this.name}] ${t}`;return r!==void 0&&(r instanceof Error?(o+=` ${r.name}: ${r.message}`,r.stack&&(o+=`
2
+ ${r.stack}`)):typeof r=="object"?o+=` ${JSON.stringify(r,Pt)}`:o+=` ${r}`),o}log(e,t,r){if(!this.shouldLog(e))return;let i=this.formatMessage(e,t,r);if(this.logFile)try{z.appendFileSync(this.logFile,i+`
3
+ `)}catch{}if(this.enableConsole)switch(e){case"error":console.error(i);break;case"warn":console.warn(i);break;default:console.log(i)}}debug(e,t){this.log("debug",e,t)}info(e,t){this.log("info",e,t)}warn(e,t){this.log("warn",e,t)}error(e,t){this.log("error",e,t)}setLevel(e){this.level=e}};c=new W({name:"codevibe-core",logFile:le.join(Ge.tmpdir(),"codevibe-core.log"),level:"info"})});var B=x(()=>{"use strict";Je()});function Ot(n){for(let e of n)try{process.stderr.write(e+`
4
+ `)}catch{}}function Nt(){we=be.join(je.homedir(),".codevibe");try{N.mkdirSync(we,{recursive:!0,mode:448})}catch{}F="file"}function Ut(){if(F!==null||ke!==null)return;let optedIn=process.env.CODEVIBE_ALLOW_FILE_KEYCHAIN==="1";if(optedIn){Ot(["","\u26A0 CodeVibe: file-based credential storage selected (CODEVIBE_ALLOW_FILE_KEYCHAIN=1).","\u26A0 Location: ~/.codevibe/ (directory 0700, files 0600)","\u26A0 Trust level: equivalent to ~/.ssh/id_rsa \u2014 weaker than OS keyring.","\u26A0 To use the OS keyring instead, unset CODEVIBE_ALLOW_FILE_KEYCHAIN and","\u26A0 install libsecret-1-0 + a running keyring daemon (Linux) or use the","\u26A0 native Keychain (macOS) / Credential Manager (Windows).",""]),c.warn("[keychain-backend] Using file-based storage at ~/.codevibe (CODEVIBE_ALLOW_FILE_KEYCHAIN=1 explicit opt-in)"),Nt();return}let keytarLoadError=null;try{let nodeRequire=eval("require");D=nodeRequire("keytar")}catch(n){keytarLoadError=n instanceof Error?n.message:String(n),D=null}if(D){F="keytar",c.info("[keychain-backend] Using keytar (OS-native keyring)");return}ke=new pe(["CodeVibe could not load the OS-native keyring (keytar).",`Reason: ${keytarLoadError??"unknown"}`,"","Options to fix this:"," 1. (Linux) Install libsecret and a keyring daemon:"," sudo apt install libsecret-1-0 gnome-keyring"," Then unlock the keyring for your user session.",""," 2. (Headless / CI / Docker) Opt in to file-based credential"," storage at ~/.codevibe/ (0600 files). This is equivalent"," in trust to ~/.ssh/id_rsa \u2014 not the OS keyring:"," export CODEVIBE_ALLOW_FILE_KEYCHAIN=1"].join(`
5
+ `))}function $t(n){return n.replace(/[^a-zA-Z0-9._-]/g,"_")}function ze(n){return be.join(we,`${$t(n)}.json`)}function Ee(n){try{let e=N.readFileSync(ze(n),"utf-8"),t=JSON.parse(e);return t&&typeof t=="object"?t:{}}catch{return{}}}function Ye(n,e){let t=ze(n);N.writeFileSync(t,JSON.stringify(e,null,2),{mode:384});try{N.chmodSync(t,384)}catch{}}function Ie(){if(Ut(),F===null)throw ke??new pe("Keychain backend not initialized")}async function Ae(n,e){return Ie(),F==="keytar"&&D?D.getPassword(n,e):Ee(n)[e]??null}async function Te(n,e,t){if(Ie(),F==="keytar"&&D){await D.setPassword(n,e,t);return}let r=Ee(n);r[e]=t,Ye(n,r)}async function Ce(n,e){if(Ie(),F==="keytar"&&D)return D.deletePassword(n,e);let t=Ee(n);return e in t?(delete t[e],Ye(n,t),!0):!1}var je,be,N,pe,F,D,we,ke,Xe=x(()=>{"use strict";je=v(require("os")),be=v(require("path")),N=v(require("fs"));B();pe=class extends Error{constructor(e){super(e),this.name="KeychainBackendUnavailableError"}},F=null,D=null,we="",ke=null});var T,_,xe,Mt,Y,E,Ze=x(()=>{"use strict";T=v(require("crypto")),_=class extends Error{constructor(e){super(e),this.name="CryptoError"}},xe=1,Mt="CodeVibe E2E v1",Y=class n{constructor(){}static getInstance(){return n.instance||(n.instance=new n),n.instance}generateKeyPair(){let e=T.createECDH("prime256v1");e.generateKeys();let r=e.getPublicKey().subarray(1).toString("base64");return{privateKey:e.getPrivateKey().toString("base64"),publicKey:r}}generateSessionKey(){return T.randomBytes(32).toString("base64")}deriveSharedKey(e,t){try{let r=T.createECDH("prime256v1"),i=Buffer.from(e,"base64");r.setPrivateKey(i);let s=Buffer.from(t,"base64"),o=s.length===65&&s[0]===4?s:Buffer.concat([Buffer.from([4]),s]),a=r.computeSecret(o),d=T.hkdfSync("sha256",a,Buffer.alloc(0),Buffer.from(Mt,"utf8"),32);return Buffer.from(d)}catch(r){throw new _(`Failed to derive shared key: ${r}`)}}encryptSessionKey(e,t){let r=this.generateKeyPair(),i=this.deriveSharedKey(r.privateKey,t),s=Buffer.from(e,"base64");return{encryptedKey:this.encrypt(s,i).toString("base64"),ephemeralPublicKey:r.publicKey}}decryptSessionKey(e,t){let r=this.deriveSharedKey(t,e.ephemeralPublicKey),i=Buffer.from(e.encryptedKey,"base64");return this.decrypt(i,r).toString("base64")}encryptContent(e,t){let r=Buffer.from(t,"base64"),i=Buffer.from(e,"utf8");return this.encrypt(i,r).toString("base64")}decryptContent(e,t){let r=Buffer.from(t,"base64"),i=Buffer.from(e,"base64");return this.decrypt(i,r).toString("utf8")}encryptMetadata(e,t){let r=JSON.stringify(e);return this.encryptContent(r,t)}decryptMetadata(e,t){let r=this.decryptContent(e,t);return JSON.parse(r)}encryptData(e,t){let r=Buffer.from(t,"base64");return this.encrypt(e,r)}decryptData(e,t){let r=Buffer.from(t,"base64");return this.decrypt(e,r)}encrypt(e,t){let r=T.randomBytes(12),i=T.createCipheriv("aes-256-gcm",t,r),s=Buffer.concat([i.update(e),i.final()]),o=i.getAuthTag();return Buffer.concat([r,s,o])}decrypt(e,t){let r=e.subarray(0,12),i=e.subarray(e.length-16),s=e.subarray(12,e.length-16),o=T.createDecipheriv("aes-256-gcm",t,r);o.setAuthTag(i);try{return Buffer.concat([o.update(s),o.final()])}catch{throw new _("Decryption failed: Invalid ciphertext or authentication tag")}}serializePrivateKey(e){return e}deserializePrivateKey(e){return e}},E=Y.getInstance()});var te=x(()=>{"use strict";Ze()});function I(){let n=process.env.ENVIRONMENT;return n==="development"||n==="production"?n:"production"}function ye(n){let e=n||I();return ue={...q[e],aws:{...q[e].aws,region:process.env.AWS_REGION||q[e].aws.region,appsyncUrl:process.env.APPSYNC_URL||q[e].aws.appsyncUrl,cognitoUserPoolId:process.env.COGNITO_USER_POOL_ID||q[e].aws.cognitoUserPoolId,cognitoClientId:process.env.COGNITO_CLIENT_ID||q[e].aws.cognitoClientId,cognitoDomain:process.env.COGNITO_DOMAIN||q[e].aws.cognitoDomain}},Qe=!0,ue}function w(){return(!Qe||!ue)&&ye(),ue}var re,ne,q,ue,Qe,et=x(()=>{"use strict";re=v(require("os")),ne=v(require("path")),q={development:{environment:"development",aws:{region:"us-east-1",appsyncUrl:"https://api-dev.codevibe.quantiya.ai/graphql",cognitoUserPoolId:"us-east-1_yVwWDPvvJ",cognitoClientId:"e9r5apv6v5uui3l928r2ris0r",cognitoDomain:"codevibe-development.auth.us-east-1.amazoncognito.com"},keychain:{serviceName:"ai.quantiya.app.codevibe"},server:{port:3456,host:"127.0.0.1",dynamicPort:!0},claude:{command:"claude",defaultTimeout:6e4},codex:{command:"codex",defaultTimeout:6e4,sessionsDir:ne.default.join(re.default.homedir(),".codex","sessions"),approvalTimeoutMs:5e3},gemini:{command:"gemini",defaultTimeout:6e4,transcriptDir:ne.default.join(re.default.homedir(),".gemini","tmp")}},production:{environment:"production",aws:{region:"us-east-1",appsyncUrl:"https://api.codevibe.quantiya.ai/graphql",cognitoUserPoolId:"us-east-1_mNRO0j5og",cognitoClientId:"5p04dbc9ojptc5r8n7605fg78f",cognitoDomain:"codevibe-production.auth.us-east-1.amazoncognito.com"},keychain:{serviceName:"ai.quantiya.app.codevibe"},server:{port:3456,host:"127.0.0.1",dynamicPort:!0},claude:{command:"claude",defaultTimeout:6e4},codex:{command:"codex",defaultTimeout:6e4,sessionsDir:ne.default.join(re.default.homedir(),".codex","sessions"),approvalTimeoutMs:5e3},gemini:{command:"gemini",defaultTimeout:6e4,transcriptDir:ne.default.join(re.default.homedir(),".gemini","tmp")}}},ue=null,Qe=!1});var X=x(()=>{"use strict";et()});var ge,tt,K,De,Wt,H,y,rt=x(()=>{"use strict";ge=v(require("os")),tt=require("uuid");Xe();te();X();B();K=class extends Error{constructor(e){super(e),this.name="KeychainError"}},De="device-identity",Wt="tokens-",H=class n{constructor(){this.deviceIdentity=null;this.sessionKeyCache=new Map;this.isRegistered=!1;this._serviceName=null}get serviceName(){return this._serviceName||(this._serviceName=w().keychain.serviceName),this._serviceName}static getInstance(){return n.instance||(n.instance=new n),n.instance}async getDeviceIdentity(){if(this.deviceIdentity)return this.deviceIdentity;let e=await Ae(this.serviceName,De);return e?(this.deviceIdentity=JSON.parse(e),c.info(`[KeychainManager] Loaded device identity: ${this.deviceIdentity.deviceId}`),this.deviceIdentity):null}async setDeviceIdentity(e){try{await Te(this.serviceName,De,JSON.stringify(e)),this.deviceIdentity=e,c.info(`[KeychainManager] Saved device identity: ${e.deviceId}`)}catch(t){throw c.error(`[KeychainManager] Failed to save device identity: ${t}`),new K(`Failed to save device identity: ${t}`)}}async getOrCreateDeviceIdentity(){let e=await this.getDeviceIdentity();if(e)return e;let t=E.generateKeyPair();return e={deviceId:(0,tt.v4)().toUpperCase(),privateKey:t.privateKey,publicKey:t.publicKey,createdAt:new Date().toISOString()},await this.setDeviceIdentity(e),c.info(`[KeychainManager] Generated new device identity: ${e.deviceId}`),e}async getDeviceId(){return(await this.getOrCreateDeviceIdentity()).deviceId}async getDevicePublicKey(){return(await this.getOrCreateDeviceIdentity()).publicKey}async getDevicePrivateKey(){return(await this.getOrCreateDeviceIdentity()).privateKey}async hasDeviceIdentity(){return await this.getDeviceIdentity()!==null}async deleteDeviceIdentity(){try{await Ce(this.serviceName,De),this.deviceIdentity=null,this.sessionKeyCache.clear(),this.isRegistered=!1,c.info("[KeychainManager] Deleted device identity")}catch(e){throw c.error(`[KeychainManager] Failed to delete device identity: ${e}`),new K(`Failed to delete device identity: ${e}`)}}getTokenAccount(e){return`${Wt}${e}`}async getTokens(e="production"){let t=await Ae(this.serviceName,this.getTokenAccount(e));if(!t)return null;let r=JSON.parse(t);return c.debug(`[KeychainManager] Loaded tokens for ${e}`),r}async setTokens(e,t="production"){try{await Te(this.serviceName,this.getTokenAccount(t),JSON.stringify(e)),c.info(`[KeychainManager] Saved tokens for ${t}`,{userId:e.userId,email:e.email})}catch(r){throw c.error(`[KeychainManager] Failed to save tokens: ${r}`),new K(`Failed to save tokens: ${r}`)}}async deleteTokens(e="production"){try{let t=await Ce(this.serviceName,this.getTokenAccount(e));return t&&c.info(`[KeychainManager] Deleted tokens for ${e}`),t}catch(t){return c.error(`[KeychainManager] Failed to delete tokens: ${t}`),!1}}isTokenExpired(e){return Date.now()>=e.expiresAt-3e5}async getSessionKey(e,t){let r=this.sessionKeyCache.get(e);if(r)return r;if(!t||t.length===0)return null;let i=await this.getDeviceId(),s=t.find(d=>d.deviceId===i);if(!s)return c.warn(`[KeychainManager] Device ${i} not found in encryptedKeys`),null;let o=await this.getDevicePrivateKey(),a=E.decryptSessionKey(s,o);return this.sessionKeyCache.set(e,a),c.info(`[KeychainManager] Decrypted and cached session key for ${e}`),a}createSessionKey(e,t){let r=E.generateSessionKey(),i=[],s=[];for(let o of e)try{let a=E.encryptSessionKey(r,o.publicKey);i.push({deviceId:o.deviceId,encryptedKey:a.encryptedKey,ephemeralPublicKey:a.ephemeralPublicKey})}catch(a){c.warn("[KeychainManager] Skipping device with invalid public key",{deviceId:o.deviceId,error:a instanceof Error?a.message:String(a)}),s.push(o.deviceId);try{t?.onDeviceSkipped?.(s.length)}catch{}}if(i.length===0)throw new _(`Failed to encrypt session key for any of ${e.length} devices`);return c.info("[KeychainManager] Created session key",{encryptedCount:i.length,skippedCount:s.length,totalCount:e.length}),{sessionKey:r,encryptedKeys:i,skippedDeviceIds:s}}cacheSessionKey(e,t){this.sessionKeyCache.set(e,t)}getCachedSessionKey(e){return this.sessionKeyCache.get(e)??null}getCachedSessionIds(){return Array.from(this.sessionKeyCache.keys())}clearSessionKey(e){this.sessionKeyCache.delete(e)}clearAllSessionKeys(){this.sessionKeyCache.clear()}getIsRegistered(){return this.isRegistered}setIsRegistered(e){this.isRegistered=e}getDeviceName(){return ge.hostname()||"CLI Client"}getDevicePlatform(){let e=ge.platform();return e==="darwin"?"MACOS":e==="linux"?"LINUX":e==="win32"?"WINDOWS":"CLI"}async clearAllData(){await this.deleteDeviceIdentity(),await this.deleteTokens("development"),await this.deleteTokens("production"),this.sessionKeyCache.clear(),this.isRegistered=!1,c.info("[KeychainManager] Cleared all data")}},y=H.getInstance()});var nt={};qe(nt,{KeychainError:()=>K,KeychainManager:()=>H,keychainManager:()=>y});var R=x(()=>{"use strict";rt()});var cr={};qe(cr,{AgentType:()=>ct,AppSyncClient:()=>ie,AuthService:()=>ee,CryptoError:()=>_,CryptoService:()=>Y,DeliveryStatus:()=>at,ENCRYPTION_VERSION:()=>xe,EventSource:()=>_e,EventType:()=>ot,KeychainError:()=>K,KeychainManager:()=>H,Logger:()=>W,SessionStatus:()=>he,_resetPrepareEventTimestampForTesting:()=>Fe,authService:()=>P,createLogger:()=>Se,cryptoService:()=>E,errorWasBeaconed:()=>oe,fireAuthCompletedBeacon:()=>se,fireAuthFailedBeacon:()=>b,getConfig:()=>w,getEnvironment:()=>I,getErrorReason:()=>Pe,keychainManager:()=>y,loadConfig:()=>ye,logger:()=>c,markErrorBeaconed:()=>A,mutations:()=>C,normalizeSnapshot:()=>Le,parseInteractivePrompt:()=>bt,prepareEventTimestamp:()=>Be,prepareSessionEncryption:()=>ve,queries:()=>U,registerDeviceEncryptionKey:()=>ce,rekeySessionForNewDevices:()=>j,resumeOrCreateSession:()=>Me,runAuthCli:()=>fe,startDeviceKeyWatcher:()=>We,subscriptions:()=>V});module.exports=Rt(cr);R();te();var G=v(require("ws")),J=require("uuid");X();B();R();var it=v(require("dns")),st=v(require("fs"));if(Bt())try{it.setDefaultResultOrder("ipv4first")}catch{}function Bt(){if(process.platform!=="linux")return!1;try{let n=st.readFileSync("/proc/sys/kernel/osrelease","utf8");return/microsoft|wsl/i.test(n)}catch{return!1}}async function Z(n,e,t){try{return await fetch(n,e)}catch(r){let i=r?.cause?.code,s=r?.cause?.message,o=i||s||r?.message||"unknown",a=Ft(i),d=t?`${t}: `:"",l=`Node ${process.version} on ${process.platform}`,g=[`${d}Cannot reach ${n}`,` Underlying error: ${o}`];a&&g.push(` Suggested fix: ${a}`),g.push(` Platform: ${l}`);let p=new Error(g.join(`
6
+ `));throw p.cause=r,p}}function Ft(n){if(!n)return null;switch(n){case"ENOTFOUND":case"EAI_AGAIN":return'DNS resolution failed. On WSL Ubuntu, check /etc/resolv.conf, or try running with NODE_OPTIONS="--dns-result-order=ipv4first".';case"ETIMEDOUT":case"ECONNREFUSED":case"ECONNRESET":case"EHOSTUNREACH":case"ENETUNREACH":return`Network unreachable. On WSL Ubuntu, try NODE_OPTIONS="--dns-result-order=ipv4first" (WSL's IPv6 is often broken). If behind a corporate proxy, set HTTPS_PROXY.`;case"CERT_HAS_EXPIRED":case"CERT_NOT_YET_VALID":return"TLS certificate time error \u2014 likely system clock drift. On WSL, run `sudo hwclock -s`, or shut down WSL from PowerShell with `wsl --shutdown` and restart.";case"UNABLE_TO_GET_ISSUER_CERT_LOCALLY":case"SELF_SIGNED_CERT_IN_CHAIN":case"UNABLE_TO_VERIFY_LEAF_SIGNATURE":case"DEPTH_ZERO_SELF_SIGNED_CERT":return"Corporate HTTPS proxy detected \u2014 the TLS cert is not trusted by Node. Set NODE_EXTRA_CA_CERTS=/path/to/corporate-ca.pem, or configure HTTPS_PROXY if a proxy is required.";default:return null}}var U={getSession:`
7
7
  query GetSession($sessionId: ID!) {
8
8
  getSession(sessionId: $sessionId) {
9
9
  sessionId
@@ -155,7 +155,7 @@ ${r.stack}`)):typeof r=="object"?o+=` ${JSON.stringify(r,_t)}`:o+=` ${r}`),o}log
155
155
  expiresAt
156
156
  }
157
157
  }
158
- `},H={onEventCreated:`
158
+ `},V={onEventCreated:`
159
159
  subscription OnEventCreated($sessionId: ID!) {
160
160
  onEventCreated(sessionId: $sessionId) {
161
161
  eventId
@@ -202,7 +202,7 @@ ${r.stack}`)):typeof r=="object"?o+=` ${JSON.stringify(r,_t)}`:o+=` ${r}`),o}log
202
202
  updatedAt
203
203
  }
204
204
  }
205
- `};var nt=(a=>(a.USER_PROMPT="USER_PROMPT",a.ASSISTANT_RESPONSE="ASSISTANT_RESPONSE",a.TOOL_USE="TOOL_USE",a.NOTIFICATION="NOTIFICATION",a.INTERACTIVE_PROMPT="INTERACTIVE_PROMPT",a.PROMPT_RESPONSE="PROMPT_RESPONSE",a.REASONING="REASONING",a))(nt||{}),De=(t=>(t.DESKTOP="DESKTOP",t.MOBILE="MOBILE",t))(De||{}),it=(r=>(r.SENT="SENT",r.DELIVERED="DELIVERED",r.EXECUTED="EXECUTED",r))(it||{});var he=(r=>(r.ACTIVE="ACTIVE",r.INACTIVE="INACTIVE",r.PAUSED="PAUSED",r))(he||{}),st=(r=>(r.CLAUDE="CLAUDE",r.GEMINI="GEMINI",r.CODEX="CODEX",r))(st||{});var k={urgentMaxAttempts:10,baseDelayMs:1e3,maxDelayMs:6e4,backoffMultiplier:2,persistentDelayMs:300*1e3},ne=class n{constructor(){this.authenticated=!1;this.currentUserId=null;this.currentEmail=null;this.tokens=null;this.activeSubscriptions=new Map;this.pendingRefresh=null;this.lastRefreshFailureAt=null;this.deviceKeyWatcher=null;this.sessionUpdateWatchers=new Map;this.heartbeatTimers=new Map;this.environment=I(),c.info("[AppSyncClient] Initialized",{environment:this.environment})}static{this.REFRESH_BACKOFF_MS=3e4}getCurrentUserId(){if(!this.currentUserId)throw new Error("Not authenticated. Call authenticateWithStoredTokens() first.");return this.currentUserId}getCurrentUserEmail(){return this.currentEmail}async authenticateWithStoredTokens(){try{let e=await y.getTokens(this.environment);if(!e)return c.debug("[AppSyncClient] No stored tokens found"),!1;if(c.info("[AppSyncClient] Found stored OAuth tokens",{userId:e.userId,email:e.email,expired:y.isTokenExpired(e)}),y.isTokenExpired(e)){if(c.info("[AppSyncClient] Tokens expired, attempting refresh..."),!await this.refreshTokens(e))return c.warn("[AppSyncClient] Token refresh failed"),!1}else this.tokens=e;return this.currentUserId=this.tokens.userId,this.currentEmail=this.tokens.email,this.authenticated=!0,c.info("[AppSyncClient] Authenticated successfully",{userId:this.currentUserId,email:this.currentEmail}),!0}catch(e){return c.error("[AppSyncClient] Authentication failed:",e),!1}}async refreshTokens(e){if(this.pendingRefresh)return this.pendingRefresh;if(this.lastRefreshFailureAt!==null&&Date.now()-this.lastRefreshFailureAt<n.REFRESH_BACKOFF_MS)return!1;this.pendingRefresh=this.performRefresh(e);try{return await this.pendingRefresh}finally{this.pendingRefresh=null}}async performRefresh(e){let t=await this.callCognitoRefresh(e.refreshToken);if(t!==null)return this.applyRefreshedTokens(e,t);let r=null;try{r=await y.getTokens(this.environment)}catch(i){c.warn("[AppSyncClient] Failed to re-read tokens from storage during refresh recovery",{error:i instanceof Error?i.message:String(i)})}if(r&&r.refreshToken&&r.refreshToken!==e.refreshToken){c.info("[AppSyncClient] In-memory refresh token rejected; retrying with storage-backed token (likely out-of-band re-auth)");let i=await this.callCognitoRefresh(r.refreshToken);if(i!==null)return this.applyRefreshedTokens(r,i)}return this.lastRefreshFailureAt=Date.now(),!1}async callCognitoRefresh(e){try{let t=w(),r=`https://${t.aws.cognitoDomain}/oauth2/token`,i=new URLSearchParams({grant_type:"refresh_token",client_id:t.aws.cognitoClientId,refresh_token:e}),s=await X(r,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:i.toString()},"Token refresh");return s.ok?await s.json():(c.error("[AppSyncClient] Token refresh failed",{status:s.status}),null)}catch(t){return c.error("[AppSyncClient] Token refresh error:",t),null}}async applyRefreshedTokens(e,t){let r={...e,accessToken:t.access_token,idToken:t.id_token,expiresAt:Date.now()+t.expires_in*1e3};this.tokens=r,this.lastRefreshFailureAt=null;try{await y.setTokens(r,this.environment),c.info("[AppSyncClient] Tokens refreshed",{expiresAt:new Date(r.expiresAt).toISOString()})}catch(i){c.warn("[AppSyncClient] Tokens refreshed but persistence failed; daemon keeps using fresh tokens in memory. A restart while persistence is still broken would lose them.",{error:i instanceof Error?i.message:String(i),expiresAt:new Date(r.expiresAt).toISOString()})}return!0}isAuthenticated(){return this.authenticated}signOut(){this.authenticated=!1,this.tokens=null,this.currentUserId=null,this.currentEmail=null,this.cleanupSubscriptions(),c.info("[AppSyncClient] Signed out")}async graphqlRequest(e,t,r=!1){let i=w();if(!this.tokens?.idToken)throw new Error('Not authenticated. Run "codevibe login" first.');let s={"Content-Type":"application/json",Authorization:this.tokens.idToken},o=await X(i.aws.appsyncUrl,{method:"POST",headers:s,body:JSON.stringify({query:e,variables:t})},"AppSync GraphQL request"),a=await o.json();if(o.status===401&&!r&&this.tokens){if(c.info("[AppSyncClient] 401 Unauthorized, refreshing token..."),await this.refreshTokens(this.tokens))return this.graphqlRequest(e,t,!0);throw new Error("Token expired and refresh failed")}if(!o.ok)throw new Error(`GraphQL request failed: ${o.status}`);if(a.errors?.length)throw new Error(`GraphQL error: ${a.errors[0].message}`);return a}async createSession(e){let t={...e,metadata:e.metadata?JSON.stringify(e.metadata):void 0},r=await this.graphqlRequest(C.createSession,{input:t});return c.info("[AppSyncClient] Session created",{sessionId:r.data.createSession.sessionId}),r.data.createSession}async updateSession(e){let t={...e,metadata:e.metadata?JSON.stringify(e.metadata):void 0},r=await this.graphqlRequest(C.updateSession,{input:t});return c.debug("[AppSyncClient] Session updated",{sessionId:r.data.updateSession.sessionId}),r.data.updateSession}async getSession(e){return(await this.graphqlRequest(U.getSession,{sessionId:e})).data.getSession}async createEvent(e){let t={...e,metadata:e.metadata?JSON.stringify(e.metadata):void 0},r=await this.graphqlRequest(C.createEvent,{input:t});return c.debug("[AppSyncClient] Event created",{eventId:r.data.createEvent.eventId,type:r.data.createEvent.type}),r.data.createEvent}async updateEventStatus(e){return(await this.graphqlRequest(C.updateEventStatus,{input:e})).data.updateEventStatus}async listEvents(e,t,r){return(await this.graphqlRequest(U.listEvents,{sessionId:e,source:t,limit:r})).data.listEvents.items}async listSessions(e=100){if(!this.currentUserId)throw new Error("Not authenticated");let t=[],r=null;do{let s=(await this.graphqlRequest(U.listSessions,{userId:this.currentUserId,limit:e,nextToken:r})).data?.listSessions;s?.items&&t.push(...s.items),r=s?.nextToken??null}while(r);return t}async sweepOrphanSessions(e){let t=e.staleThresholdMs??9e5,r=new Set(e.excludeSessionIds??[]),i=Date.now(),s;try{s=await this.listSessions()}catch(a){return c.warn("[AppSyncClient] OrphanSweep: listSessions failed, skipping sweep",{agentType:e.agentType,error:a instanceof Error?a.message:String(a)}),0}let o=0;for(let a of s){if(a.agentType!==e.agentType||a.status!=="ACTIVE"||r.has(a.sessionId)||!a.lastHeartbeatAt)continue;let d=i-new Date(a.lastHeartbeatAt).getTime();if(!(d<t)){c.warn("[AppSyncClient] OrphanSweep: marking stale session INACTIVE",{sessionId:a.sessionId,agentType:a.agentType,lastHeartbeatAt:a.lastHeartbeatAt,heartbeatAgeMinutes:Math.round(d/6e4)});try{await this.updateSession({sessionId:a.sessionId,status:"INACTIVE"}),o++}catch(l){c.warn("[AppSyncClient] OrphanSweep: updateSession failed, leaving row as-is",{sessionId:a.sessionId,error:l instanceof Error?l.message:String(l)})}}}return o>0&&c.info("[AppSyncClient] OrphanSweep complete",{agentType:e.agentType,swept:o}),o}async listUserDeviceKeys(){return(await this.graphqlRequest(U.listUserDeviceKeys,{})).data.listUserDeviceKeys||[]}async registerDeviceKey(e,t,r,i){let s={deviceId:e,publicKey:t,platform:r,deviceName:i};await this.graphqlRequest(C.registerDeviceKey,{input:s}),c.info("[AppSyncClient] Device key registered",{deviceId:e,platform:r})}async grantSessionKey(e){await this.graphqlRequest(C.grantSessionKey,{input:e}),c.info("[AppSyncClient] Session key granted",{sessionId:e.sessionId,deviceId:e.deviceId})}async getAttachmentDownloadUrl(e){return(await this.graphqlRequest(C.getAttachmentDownloadUrl,{s3Key:e})).data.getAttachmentDownloadUrl}subscribeToEvents(e,t,r){c.info("[AppSyncClient] Subscribing to events",{sessionId:e});let i=this.activeSubscriptions.get(e);i&&(this.cleanupSubscriptionState(i),this.activeSubscriptions.delete(e));let s={ws:null,subscriptionId:(0,J.v4)(),sessionId:e,onEvent:t,onError:r,reconnectAttempts:0,isReconnecting:!1,destroyed:!1};return this.activeSubscriptions.set(e,s),this.createSubscription(s),()=>{this.cleanupSubscriptionState(s),this.activeSubscriptions.delete(e)}}buildRealtimeUrl(){let e=w(),t=new URL(e.aws.appsyncUrl),i=/\.appsync-api\.[^.]+\.amazonaws\.com$/.test(t.host)?e.aws.appsyncUrl.replace("https://","wss://").replace("appsync-api","appsync-realtime-api"):`wss://${t.host}/graphql/realtime`,s={host:t.host};this.tokens?.idToken&&(s.Authorization=this.tokens.idToken);let o=Buffer.from(JSON.stringify(s)).toString("base64"),a=Buffer.from(JSON.stringify({})).toString("base64");return`${i}?header=${o}&payload=${a}`}createSubscription(e){let{sessionId:t,subscriptionId:r,onEvent:i,onError:s}=e;try{let o=this.buildRealtimeUrl(),a=new V.default(o,["graphql-ws"]);a.on("open",()=>{c.info("[AppSyncClient] WebSocket connected",{sessionId:t}),a.send(JSON.stringify({type:"connection_init"}))}),a.on("message",d=>{try{let l=JSON.parse(d.toString());switch(l.type){case"connection_ack":this.sendSubscriptionStart(a,e);break;case"start_ack":c.info("[AppSyncClient] Subscription started",{sessionId:t}),e.isReconnecting=!1,e.reconnectAttempts=0,this.startHeartbeat(t);break;case"data":this.resetKeepAliveTimer(e);let h=l.payload?.data?.onEventCreated;h&&h.source==="MOBILE"&&i(h);break;case"ka":this.resetKeepAliveTimer(e);break;case"error":let p=l.payload?.errors?.[0]?.message||"Unknown error";this.handleSubscriptionError(e,new Error(p));break}}catch(l){c.error("[AppSyncClient] Failed to parse message",{error:l})}}),a.on("error",d=>{c.error("[AppSyncClient] WebSocket error",{sessionId:t,error:d.message}),this.handleSubscriptionError(e,d)}),a.on("close",(d,l)=>{c.info("[AppSyncClient] WebSocket closed",{sessionId:t,code:d}),e.keepAliveTimer&&clearTimeout(e.keepAliveTimer),!e.destroyed&&this.activeSubscriptions.get(t)===e&&this.handleSubscriptionError(e,new Error(`WebSocket closed: ${d}`))}),e.ws=a,this.resetKeepAliveTimer(e)}catch(o){this.handleSubscriptionError(e,o)}}sendSubscriptionStart(e,t){let r=w(),{sessionId:i,subscriptionId:s}=t,o={host:new URL(r.aws.appsyncUrl).host};this.tokens?.idToken&&(o.Authorization=this.tokens.idToken),e.send(JSON.stringify({id:s,type:"start",payload:{data:JSON.stringify({query:H.onEventCreated,variables:{sessionId:i}}),extensions:{authorization:o}}}))}resetKeepAliveTimer(e){e.keepAliveTimer&&clearTimeout(e.keepAliveTimer),e.keepAliveTimer=setTimeout(()=>{this.handleSubscriptionError(e,new Error("Keep-alive timeout"))},300*1e3)}handleSubscriptionError(e,t){let{sessionId:r,onError:i}=e;if(e.isReconnecting||!this.activeSubscriptions.has(r))return;e.isReconnecting=!0,e.reconnectAttempts++,this.stopHeartbeat(r);let s=e.reconnectAttempts<=k.urgentMaxAttempts,o;if(s?o=Math.min(k.baseDelayMs*Math.pow(k.backoffMultiplier,e.reconnectAttempts-1),k.maxDelayMs):(o=k.persistentDelayMs,e.reconnectAttempts===k.urgentMaxAttempts+1&&c.info("[AppSyncClient] Switching to persistent reconnect (every 5min)",{sessionId:r})),c.info("[AppSyncClient] Scheduling reconnect",{sessionId:r,attempt:e.reconnectAttempts,phase:s?"urgent":"persistent",delayMs:o}),e.ws){try{e.ws.close(1e3)}catch{}e.ws=null}e.keepAliveTimer&&clearTimeout(e.keepAliveTimer),e.reconnectTimer=setTimeout(async()=>{if(e.isReconnecting=!1,e.destroyed||this.activeSubscriptions.get(r)!==e){c.info("[AppSyncClient] Reconnect skipped \u2014 state is no longer canonical",{sessionId:r});return}try{let a=await y.getTokens(this.environment);a&&(y.isTokenExpired(a)?await this.refreshTokens(a)&&c.info("[AppSyncClient] Tokens refreshed before reconnect",{sessionId:r}):this.tokens=a)}catch{c.warn("[AppSyncClient] Token refresh failed before reconnect, using existing tokens",{sessionId:r})}if(e.destroyed||this.activeSubscriptions.get(r)!==e){c.info("[AppSyncClient] Reconnect skipped after token refresh \u2014 state no longer canonical",{sessionId:r});return}e.subscriptionId=(0,J.v4)(),this.createSubscription(e)},o)}cleanupSubscriptionState(e){if(e.destroyed=!0,e.reconnectTimer&&(clearTimeout(e.reconnectTimer),e.reconnectTimer=void 0),e.keepAliveTimer&&(clearTimeout(e.keepAliveTimer),e.keepAliveTimer=void 0),e.ws){try{e.ws.readyState===V.default.OPEN&&e.ws.send(JSON.stringify({id:e.subscriptionId,type:"stop"}))}catch{}try{e.ws.close(1e3)}catch{}try{e.ws.removeAllListeners()}catch{}e.ws=null}}subscribeToDeviceKeyRegistered(e,t,r,i){c.info("[AppSyncClient] Subscribing to device key registrations",{userId:e}),this.deviceKeyWatcher&&this.stopDeviceKeyWatcherInternal();let s={userId:e,subscriptionId:(0,J.v4)(),ws:null,onNewDevice:t,onReconnect:r,onError:i,reconnectAttempts:0,isReconnecting:!1,destroyed:!1};return this.deviceKeyWatcher=s,this.createDeviceKeyWatcherConnection(s),()=>{this.stopDeviceKeyWatcherInternal()}}stopDeviceKeyWatcher(){this.stopDeviceKeyWatcherInternal()}stopDeviceKeyWatcherInternal(){let e=this.deviceKeyWatcher;if(e){if(e.destroyed=!0,e.reconnectTimer&&(clearTimeout(e.reconnectTimer),e.reconnectTimer=void 0),e.keepAliveTimer&&(clearTimeout(e.keepAliveTimer),e.keepAliveTimer=void 0),e.ws){try{e.ws.readyState===V.default.OPEN&&e.ws.send(JSON.stringify({id:e.subscriptionId,type:"stop"}))}catch{}try{e.ws.close(1e3)}catch{}try{e.ws.removeAllListeners()}catch{}e.ws=null}this.deviceKeyWatcher=null,c.info("[AppSyncClient] Device key watcher stopped")}}createDeviceKeyWatcherConnection(e){try{let t=this.buildRealtimeUrl(),r=new V.default(t,["graphql-ws"]);r.on("open",()=>{c.info("[AppSyncClient] Device key watcher WebSocket connected",{userId:e.userId}),r.send(JSON.stringify({type:"connection_init"}))}),r.on("message",i=>{try{let s=JSON.parse(i.toString());switch(s.type){case"connection_ack":this.sendDeviceKeyWatcherStart(r,e);break;case"start_ack":c.info("[AppSyncClient] Device key watcher subscription started",{userId:e.userId});let o=e.isReconnecting;if(e.isReconnecting=!1,e.reconnectAttempts=0,o&&e.onReconnect)try{e.onReconnect()}catch(l){c.warn("[AppSyncClient] Device key watcher onReconnect handler threw",{error:l})}break;case"data":this.resetDeviceKeyWatcherKeepAlive(e);let a=s.payload?.data?.onDeviceKeyRegistered;if(a){c.info("[AppSyncClient] Device key registration observed",{userId:e.userId,newDeviceId:a.deviceId,platform:a.platform});try{e.onNewDevice(a)}catch(l){c.warn("[AppSyncClient] Device key watcher onNewDevice handler threw",{error:l})}}break;case"ka":this.resetDeviceKeyWatcherKeepAlive(e);break;case"error":let d=s.payload?.errors?.[0]?.message||"Unknown error";this.handleDeviceKeyWatcherError(e,new Error(d));break}}catch(s){c.error("[AppSyncClient] Failed to parse device key watcher message",{error:s})}}),r.on("error",i=>{c.error("[AppSyncClient] Device key watcher WebSocket error",{userId:e.userId,error:i.message}),this.handleDeviceKeyWatcherError(e,i)}),r.on("close",i=>{c.info("[AppSyncClient] Device key watcher WebSocket closed",{userId:e.userId,code:i}),e.keepAliveTimer&&clearTimeout(e.keepAliveTimer),!e.destroyed&&this.deviceKeyWatcher===e&&this.handleDeviceKeyWatcherError(e,new Error(`WebSocket closed: ${i}`))}),e.ws=r,this.resetDeviceKeyWatcherKeepAlive(e)}catch(t){this.handleDeviceKeyWatcherError(e,t)}}sendDeviceKeyWatcherStart(e,t){let r=w(),{userId:i,subscriptionId:s}=t,o={host:new URL(r.aws.appsyncUrl).host};this.tokens?.idToken&&(o.Authorization=this.tokens.idToken),e.send(JSON.stringify({id:s,type:"start",payload:{data:JSON.stringify({query:H.onDeviceKeyRegistered,variables:{userId:i}}),extensions:{authorization:o}}}))}resetDeviceKeyWatcherKeepAlive(e){e.keepAliveTimer&&clearTimeout(e.keepAliveTimer),e.keepAliveTimer=setTimeout(()=>{this.handleDeviceKeyWatcherError(e,new Error("Device key watcher keep-alive timeout"))},300*1e3)}handleDeviceKeyWatcherError(e,t){if(e.isReconnecting||e.destroyed||this.deviceKeyWatcher!==e)return;if(e.isReconnecting=!0,e.reconnectAttempts++,e.onError)try{e.onError(t)}catch{}if(e.ws){try{e.ws.removeAllListeners()}catch{}try{e.ws.close(1e3)}catch{}e.ws=null}e.keepAliveTimer&&(clearTimeout(e.keepAliveTimer),e.keepAliveTimer=void 0);let i=e.reconnectAttempts<=k.urgentMaxAttempts?Math.min(k.baseDelayMs*Math.pow(k.backoffMultiplier,e.reconnectAttempts-1),k.maxDelayMs):k.persistentDelayMs;c.warn("[AppSyncClient] Device key watcher reconnect scheduled",{userId:e.userId,attempts:e.reconnectAttempts,delayMs:i,error:t.message}),e.reconnectTimer=setTimeout(async()=>{if(e.isReconnecting=!1,e.destroyed||this.deviceKeyWatcher!==e){c.info("[AppSyncClient] Device key watcher reconnect skipped \u2014 state no longer canonical",{userId:e.userId});return}try{let s=await y.getTokens(this.environment);s&&(y.isTokenExpired(s)?await this.refreshTokens(s)&&c.info("[AppSyncClient] Tokens refreshed before device key watcher reconnect",{userId:e.userId}):this.tokens=s)}catch{c.warn("[AppSyncClient] Token refresh failed before device key watcher reconnect, using existing tokens",{userId:e.userId})}e.destroyed||this.deviceKeyWatcher!==e||(e.subscriptionId=(0,J.v4)(),this.createDeviceKeyWatcherConnection(e))},i)}watchForMobileEnd(e,t){c.info("[AppSyncClient] Starting mobile-end watcher",{sessionId:e});let r=this.sessionUpdateWatchers.get(e);r&&(c.info("[AppSyncClient] Replacing existing mobile-end watcher",{sessionId:e}),this.cleanupSessionUpdateWatcherState(r),this.sessionUpdateWatchers.delete(e));let i={sessionId:e,subscriptionId:(0,J.v4)(),ws:null,onMobileEndRequested:t,priorStatus:"ACTIVE",firedOnce:!1,reconnectAttempts:0,isReconnecting:!1,destroyed:!1};return this.sessionUpdateWatchers.set(e,i),this.createSessionUpdateWatcherConnection(i),{stop:()=>{this.sessionUpdateWatchers.get(e)===i&&(this.cleanupSessionUpdateWatcherState(i),this.sessionUpdateWatchers.delete(e),c.info("[AppSyncClient] Mobile-end watcher stopped",{sessionId:e}))}}}createSessionUpdateWatcherConnection(e){try{let t=this.buildRealtimeUrl(),r=new V.default(t,["graphql-ws"]);r.on("open",()=>{c.info("[AppSyncClient] Mobile-end watcher WebSocket connected",{sessionId:e.sessionId}),r.send(JSON.stringify({type:"connection_init"}))}),r.on("message",i=>{try{let s=JSON.parse(i.toString());switch(s.type){case"connection_ack":this.sendSessionUpdateWatcherStart(r,e);break;case"start_ack":c.info("[AppSyncClient] Mobile-end watcher subscription started",{sessionId:e.sessionId}),e.isReconnecting=!1,e.reconnectAttempts=0;break;case"data":this.resetSessionUpdateWatcherKeepAlive(e),this.handleSessionUpdatePayload(e,s.payload);break;case"ka":this.resetSessionUpdateWatcherKeepAlive(e);break;case"error":let o=s.payload?.errors?.[0]?.message||"Unknown error";this.handleSessionUpdateWatcherError(e,new Error(o));break}}catch(s){c.error("[AppSyncClient] Failed to parse mobile-end watcher message",{error:s})}}),r.on("error",i=>{c.error("[AppSyncClient] Mobile-end watcher WebSocket error",{sessionId:e.sessionId,error:i.message}),this.handleSessionUpdateWatcherError(e,i)}),r.on("close",i=>{c.info("[AppSyncClient] Mobile-end watcher WebSocket closed",{sessionId:e.sessionId,code:i}),e.keepAliveTimer&&clearTimeout(e.keepAliveTimer),!e.destroyed&&this.sessionUpdateWatchers.get(e.sessionId)===e&&this.handleSessionUpdateWatcherError(e,new Error(`WebSocket closed: ${i}`))}),e.ws=r,this.resetSessionUpdateWatcherKeepAlive(e)}catch(t){this.handleSessionUpdateWatcherError(e,t)}}handleSessionUpdatePayload(e,t){let r=t?.data?.onSessionUpdated;if(!r){c.warn("[AppSyncClient] Mobile-end watcher received malformed payload",{sessionId:e.sessionId});return}if(e.firedOnce)return;let i=r.status;if(i==null){c.debug("[AppSyncClient] Mobile-end watcher skipped non-status payload",{sessionId:e.sessionId});return}if(e.priorStatus==="ACTIVE"&&i==="INACTIVE"){e.firedOnce=!0,e.priorStatus="INACTIVE",c.info("[AppSyncClient] Mobile end requested for session",{sessionId:e.sessionId}),Promise.resolve().then(()=>e.onMobileEndRequested()).catch(s=>{c.warn("[AppSyncClient] Mobile-end callback threw",{sessionId:e.sessionId,error:s})});return}e.priorStatus=i}sendSessionUpdateWatcherStart(e,t){let r=w(),{sessionId:i,subscriptionId:s}=t,o={host:new URL(r.aws.appsyncUrl).host};this.tokens?.idToken&&(o.Authorization=this.tokens.idToken),e.send(JSON.stringify({id:s,type:"start",payload:{data:JSON.stringify({query:H.onSessionUpdated,variables:{sessionId:i}}),extensions:{authorization:o}}}))}resetSessionUpdateWatcherKeepAlive(e){e.keepAliveTimer&&clearTimeout(e.keepAliveTimer),e.keepAliveTimer=setTimeout(()=>{this.handleSessionUpdateWatcherError(e,new Error("Mobile-end watcher keep-alive timeout"))},300*1e3)}handleSessionUpdateWatcherError(e,t){if(e.isReconnecting||e.destroyed||this.sessionUpdateWatchers.get(e.sessionId)!==e)return;if(e.isReconnecting=!0,e.reconnectAttempts++,e.ws){try{e.ws.removeAllListeners()}catch{}try{e.ws.close(1e3)}catch{}e.ws=null}e.keepAliveTimer&&(clearTimeout(e.keepAliveTimer),e.keepAliveTimer=void 0);let i=e.reconnectAttempts<=k.urgentMaxAttempts?Math.min(k.baseDelayMs*Math.pow(k.backoffMultiplier,e.reconnectAttempts-1),k.maxDelayMs):k.persistentDelayMs;c.warn("[AppSyncClient] Mobile-end watcher reconnect scheduled",{sessionId:e.sessionId,attempts:e.reconnectAttempts,delayMs:i,error:t.message}),e.reconnectTimer=setTimeout(async()=>{if(e.isReconnecting=!1,!(e.destroyed||this.sessionUpdateWatchers.get(e.sessionId)!==e)){try{let s=await y.getTokens(this.environment);s&&(y.isTokenExpired(s)?await this.refreshTokens(s):this.tokens=s)}catch{c.warn("[AppSyncClient] Token refresh failed before mobile-end watcher reconnect",{sessionId:e.sessionId})}e.destroyed||this.sessionUpdateWatchers.get(e.sessionId)!==e||(e.subscriptionId=(0,J.v4)(),this.createSessionUpdateWatcherConnection(e))}},i)}cleanupSessionUpdateWatcherState(e){if(e.destroyed=!0,e.reconnectTimer&&(clearTimeout(e.reconnectTimer),e.reconnectTimer=void 0),e.keepAliveTimer&&(clearTimeout(e.keepAliveTimer),e.keepAliveTimer=void 0),e.ws){try{e.ws.readyState===V.default.OPEN&&e.ws.send(JSON.stringify({id:e.subscriptionId,type:"stop"}))}catch{}try{e.ws.close(1e3)}catch{}try{e.ws.removeAllListeners()}catch{}e.ws=null}}startHeartbeat(e,t=120*1e3){this.stopHeartbeat(e),this.sendHeartbeat(e);let r=setInterval(()=>{this.sendHeartbeat(e)},t);this.heartbeatTimers.set(e,r),c.info("[AppSyncClient] Heartbeat started",{sessionId:e,intervalMs:t})}stopHeartbeat(e){let t=this.heartbeatTimers.get(e);t&&(clearInterval(t),this.heartbeatTimers.delete(e),c.info("[AppSyncClient] Heartbeat stopped",{sessionId:e}))}async sendHeartbeat(e){try{await this.updateSession({sessionId:e,lastHeartbeatAt:new Date().toISOString()}),c.debug("[AppSyncClient] Heartbeat sent",{sessionId:e})}catch(t){c.warn("[AppSyncClient] Heartbeat failed",{sessionId:e,error:t})}}cleanupSubscriptions(){this.activeSubscriptions.forEach(e=>{this.cleanupSubscriptionState(e)}),this.activeSubscriptions.clear(),this.stopDeviceKeyWatcherInternal(),this.sessionUpdateWatchers.forEach(e=>{this.cleanupSessionUpdateWatcherState(e)}),this.sessionUpdateWatchers.clear(),this.heartbeatTimers.forEach(e=>clearInterval(e)),this.heartbeatTimers.clear()}};var gt=v(require("crypto")),mt=v(require("fs")),Oe=v(require("http")),ft=require("child_process");Y();R();M();var _e=v(require("crypto")),ot=v(require("https")),at=v(require("os")),Mt="G-GS74YEQTB8",Bt="lAfOF6OxRzSQ-NsLBRjhAg",Ft="www.google-analytics.com",qt=`/mp/collect?measurement_id=${Mt}&api_secret=${Bt}`,Ht={port_in_use:"server_start",server_listen_failed:"server_start",browser_open_failed:"browser_open",login_timeout:"awaiting_callback",cognito_rejected:"awaiting_callback",state_mismatch:"awaiting_callback",no_authorization_code:"awaiting_callback",token_exchange_failed:"exchanging_code",token_exchange_network_error:"exchanging_code",keychain_write_failed:"storing_tokens",user_aborted:"unknown",unknown:"unknown"};function Vt(){let n=typeof process.getuid=="function"?process.getuid():0;return _e.createHash("sha256").update(`${at.hostname()}-${n}`).digest("hex").substring(0,36)}function $(){return{platform:process.platform,source:process.env.CODEVIBE_TELEMETRY_SOURCE||"production"}}async function L(n,e){try{let t=JSON.stringify({client_id:Vt(),events:[{name:n,params:e}]});await new Promise(r=>{let i=ot.request({hostname:Ft,path:qt,method:"POST",headers:{"Content-Type":"application/json"}},()=>r());i.on("error",()=>r()),i.write(t),i.end(),setTimeout(r,2e3)})}catch{}}async function ie(n){await L("auth_completed",{...$(),user_id:n})}async function b(n,e){let t={...$(),reason:n,stage:e?.stage??Ht[n]};if(typeof e?.httpStatus=="number"&&(t.http_status=e.httpStatus),e?.errorFragment){let{homedir:r}=await import("os"),i=e.errorFragment.replace(/\x1b\[[0-9;]*[a-zA-Z]/g,"").replace(/\\/g,"/").replace(/[\n\r\t"]/g," ").replace(/[^\x20-\x7E]/g,"").trim(),s=[process.env.HOME,process.env.USERPROFILE,(()=>{try{return r()}catch{return}})()].filter(d=>typeof d=="string"&&d.length>0).map(d=>d.replace(/\\/g,"/"));for(let d of s){let l=d.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");i=i.replace(new RegExp(l,"g"),"~")}i=i.replace(/\/Users\/[^/ ]+/g,"/Users/<user>").replace(/\/home\/[^/ ]+/g,"/home/<user>").replace(/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g,"<email>");let o=i.substring(0,100),a=i.substring(100,200);o&&(t.error_fragment=o),a&&(t.error_fragment_2=a)}await L("auth_failed",t)}var Ke=Symbol.for("codevibe.auth.beaconed"),ct=Symbol.for("codevibe.auth.failureReason");function A(n,e){try{Object.defineProperty(n,Ke,{value:!0,enumerable:!1,configurable:!0,writable:!1}),Object.defineProperty(n,ct,{value:e,enumerable:!1,configurable:!0,writable:!1})}catch{}return n}function se(n){return!!(n&&typeof n=="object"&&n[Ke])}function Re(n){if(n&&typeof n=="object"&&n[Ke]){let e=n[ct];if(typeof e=="string")return e}}function Z(n){return n<=0?"0":n===1?"1":n<=5?"2-5":"6+"}function ge(n){return _e.createHash("sha256").update(n).digest("hex").slice(0,8)}async function dt(n){return L("session_encryption_device_skipped",{...$(),...n})}async function lt(n){return L("session_encryption_partial_success",{...$(),...n})}async function pt(n){return L("session_encryption_catch_up_grant",{...$(),...n})}async function ut(n){return L("session_encryption_self_rekey_request",{...$(),...n})}async function yt(n){return L("session_encryption_self_rekey_success",{...$(),...n})}async function ht(n){return L("session_encryption_self_rekey_timeout",{...$(),...n})}var oe=8080,vt="/callback",Pe=`http://localhost:${oe}${vt}`,Q=class n{constructor(){}static getInstance(){return n.instance||(n.instance=new n),n.instance}openBrowser(e){console.error(""),console.error("Opening your browser for sign-in..."),this.isRunningInWSL()?console.error("If your browser does not open, paste this URL in your Windows browser:"):console.error("If your browser does not open automatically, visit this URL:"),console.error(` ${e}`),console.error("");let t=this.getBrowserCommands();this.tryBrowserCommand(t,e,0)}getBrowserCommands(){let e=process.platform;if(e==="darwin")return[{cmd:"open",fixedArgs:[]}];if(e==="win32")return[{cmd:"cmd",fixedArgs:["/c","start",""]}];let t=[];return this.isRunningInWSL()&&(t.push({cmd:"wslview",fixedArgs:[]}),t.push({cmd:"cmd.exe",fixedArgs:["/c","start",""]}),t.push({cmd:"powershell.exe",fixedArgs:["-NoProfile","-Command","Start-Process"]})),t.push({cmd:"xdg-open",fixedArgs:[]}),t}isRunningInWSL(){if(process.platform!=="linux")return!1;try{let e=mt.readFileSync("/proc/sys/kernel/osrelease","utf8");return/microsoft|wsl/i.test(e)}catch{return!1}}tryBrowserCommand(e,t,r){if(r>=e.length){c.debug("[AuthService] No browser-opening command succeeded. User must open the sign-in URL manually (printed to stderr above)."),console.error(""),console.error("\u26A0\uFE0F Could not open browser automatically."),this.isRunningInWSL()?console.error(" WSL detected \u2014 paste this URL in your Windows browser:"):console.error(" Please copy and paste this URL into your browser:"),console.error(` ${t}`),console.error("");return}let i=e[r],s=[...i.fixedArgs,t],o=!1,a=p=>{o||(o=!0,c.debug(`[AuthService] Browser command '${i.cmd}' ${p}; trying next fallback`),this.tryBrowserCommand(e,t,r+1))},d=p=>{o||(o=!0,c.debug(`[AuthService] Browser command '${i.cmd}' ${p}`))},l;try{l=(0,ft.spawn)(i.cmd,s,{detached:!0,stdio:"ignore"})}catch(p){a(`threw synchronously: ${p?.message||p}`);return}l.on("error",p=>{a(`failed to spawn: ${p?.message||p}`)}),l.on("exit",(p,g)=>{p===0?d("exited successfully"):a(g?`terminated by signal ${g}`:`exited with code ${p}`)}),setTimeout(()=>{d("still running after 3s, assuming success")},3e3).unref(),l.unref()}generateState(){return gt.randomBytes(32).toString("hex")}buildAuthUrl(e){let t=w(),r=new URLSearchParams({client_id:t.aws.cognitoClientId,response_type:"code",scope:"email openid profile",redirect_uri:Pe,state:e});return`https://${t.aws.cognitoDomain}/oauth2/authorize?${r.toString()}`}async exchangeCodeForTokens(e){let t=w(),r=`https://${t.aws.cognitoDomain}/oauth2/token`,i=new URLSearchParams({grant_type:"authorization_code",client_id:t.aws.cognitoClientId,code:e,redirect_uri:Pe}),s;try{s=await X(r,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:i.toString()},"Token exchange")}catch(a){throw await b("token_exchange_network_error"),A(a,"token_exchange_network_error"),a}if(!s.ok){let a=await s.text(),d=new Error(`Token exchange failed: ${s.status} ${a}`);throw await b("token_exchange_failed",{httpStatus:s.status}),A(d,"token_exchange_failed"),d}let o=await s.json();return{accessToken:o.access_token,idToken:o.id_token,refreshToken:o.refresh_token,expiresIn:o.expires_in}}decodeJwt(e){let t=e.split(".");if(t.length!==3)throw new Error("Invalid JWT");return JSON.parse(Buffer.from(t[1],"base64").toString("utf-8"))}async refreshTokens(e){let t=w(),r=`https://${t.aws.cognitoDomain}/oauth2/token`,i=new URLSearchParams({grant_type:"refresh_token",client_id:t.aws.cognitoClientId,refresh_token:e}),s=await X(r,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:i.toString()},"Token refresh");if(!s.ok)throw new Error(`Token refresh failed: ${s.status}`);let o=await s.json();return{accessToken:o.access_token,idToken:o.id_token,expiresIn:o.expires_in}}async login(){let e=await y.getTokens(I());if(e&&!y.isTokenExpired(e))return e;let t=this.generateState(),r=this.buildAuthUrl(t);return new Promise((i,s)=>{let o=Oe.createServer(async(a,d)=>{if(!a.url?.startsWith(vt)){d.writeHead(404),d.end("Not found");return}try{let l=new URL(a.url,`http://localhost:${oe}`),h=l.searchParams.get("code"),p=l.searchParams.get("state"),g=l.searchParams.get("error");if(g){let S=new Error(`OAuth error: ${g}`);throw await b("cognito_rejected"),A(S,"cognito_rejected"),S}if(p!==t){let S=new Error("State mismatch");throw await b("state_mismatch"),A(S,"state_mismatch"),S}if(!h){let S=new Error("No authorization code");throw await b("no_authorization_code"),A(S,"no_authorization_code"),S}let f=await this.exchangeCodeForTokens(h),O=this.decodeJwt(f.idToken),m={accessToken:f.accessToken,idToken:f.idToken,refreshToken:f.refreshToken,expiresAt:Date.now()+f.expiresIn*1e3,userId:O.sub,email:O.email||"unknown"};try{await y.setTokens(m,I())}catch(S){throw await b("keychain_write_failed"),A(S,"keychain_write_failed"),S}d.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),d.end(`
205
+ `};var ot=(a=>(a.USER_PROMPT="USER_PROMPT",a.ASSISTANT_RESPONSE="ASSISTANT_RESPONSE",a.TOOL_USE="TOOL_USE",a.NOTIFICATION="NOTIFICATION",a.INTERACTIVE_PROMPT="INTERACTIVE_PROMPT",a.PROMPT_RESPONSE="PROMPT_RESPONSE",a.REASONING="REASONING",a))(ot||{}),_e=(t=>(t.DESKTOP="DESKTOP",t.MOBILE="MOBILE",t))(_e||{}),at=(r=>(r.SENT="SENT",r.DELIVERED="DELIVERED",r.EXECUTED="EXECUTED",r))(at||{});var he=(r=>(r.ACTIVE="ACTIVE",r.INACTIVE="INACTIVE",r.PAUSED="PAUSED",r))(he||{}),ct=(i=>(i.CLAUDE="CLAUDE",i.GEMINI="GEMINI",i.CODEX="CODEX",i.ANTIGRAVITY="ANTIGRAVITY",i))(ct||{});var k={urgentMaxAttempts:10,baseDelayMs:1e3,maxDelayMs:6e4,backoffMultiplier:2,persistentDelayMs:300*1e3},ie=class n{constructor(){this.authenticated=!1;this.currentUserId=null;this.currentEmail=null;this.tokens=null;this.activeSubscriptions=new Map;this.pendingRefresh=null;this.lastRefreshFailureAt=null;this.deviceKeyWatcher=null;this.sessionUpdateWatchers=new Map;this.heartbeatTimers=new Map;this.environment=I(),c.info("[AppSyncClient] Initialized",{environment:this.environment})}static{this.REFRESH_BACKOFF_MS=3e4}getCurrentUserId(){if(!this.currentUserId)throw new Error("Not authenticated. Call authenticateWithStoredTokens() first.");return this.currentUserId}getCurrentUserEmail(){return this.currentEmail}async authenticateWithStoredTokens(){try{let e=await y.getTokens(this.environment);if(!e)return c.debug("[AppSyncClient] No stored tokens found"),!1;if(c.info("[AppSyncClient] Found stored OAuth tokens",{userId:e.userId,email:e.email,expired:y.isTokenExpired(e)}),y.isTokenExpired(e)){if(c.info("[AppSyncClient] Tokens expired, attempting refresh..."),!await this.refreshTokens(e))return c.warn("[AppSyncClient] Token refresh failed"),!1}else this.tokens=e;return this.currentUserId=this.tokens.userId,this.currentEmail=this.tokens.email,this.authenticated=!0,c.info("[AppSyncClient] Authenticated successfully",{userId:this.currentUserId,email:this.currentEmail}),!0}catch(e){return c.error("[AppSyncClient] Authentication failed:",e),!1}}async refreshTokens(e){if(this.pendingRefresh)return this.pendingRefresh;if(this.lastRefreshFailureAt!==null&&Date.now()-this.lastRefreshFailureAt<n.REFRESH_BACKOFF_MS)return!1;this.pendingRefresh=this.performRefresh(e);try{return await this.pendingRefresh}finally{this.pendingRefresh=null}}async performRefresh(e){let t=await this.callCognitoRefresh(e.refreshToken);if(t!==null)return this.applyRefreshedTokens(e,t);let r=null;try{r=await y.getTokens(this.environment)}catch(i){c.warn("[AppSyncClient] Failed to re-read tokens from storage during refresh recovery",{error:i instanceof Error?i.message:String(i)})}if(r&&r.refreshToken&&r.refreshToken!==e.refreshToken){c.info("[AppSyncClient] In-memory refresh token rejected; retrying with storage-backed token (likely out-of-band re-auth)");let i=await this.callCognitoRefresh(r.refreshToken);if(i!==null)return this.applyRefreshedTokens(r,i)}return this.lastRefreshFailureAt=Date.now(),!1}async callCognitoRefresh(e){try{let t=w(),r=`https://${t.aws.cognitoDomain}/oauth2/token`,i=new URLSearchParams({grant_type:"refresh_token",client_id:t.aws.cognitoClientId,refresh_token:e}),s=await Z(r,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:i.toString()},"Token refresh");return s.ok?await s.json():(c.error("[AppSyncClient] Token refresh failed",{status:s.status}),null)}catch(t){return c.error("[AppSyncClient] Token refresh error:",t),null}}async applyRefreshedTokens(e,t){let r={...e,accessToken:t.access_token,idToken:t.id_token,expiresAt:Date.now()+t.expires_in*1e3};this.tokens=r,this.lastRefreshFailureAt=null;try{await y.setTokens(r,this.environment),c.info("[AppSyncClient] Tokens refreshed",{expiresAt:new Date(r.expiresAt).toISOString()})}catch(i){c.warn("[AppSyncClient] Tokens refreshed but persistence failed; daemon keeps using fresh tokens in memory. A restart while persistence is still broken would lose them.",{error:i instanceof Error?i.message:String(i),expiresAt:new Date(r.expiresAt).toISOString()})}return!0}isAuthenticated(){return this.authenticated}signOut(){this.authenticated=!1,this.tokens=null,this.currentUserId=null,this.currentEmail=null,this.cleanupSubscriptions(),c.info("[AppSyncClient] Signed out")}async graphqlRequest(e,t,r=!1){let i=w();if(!this.tokens?.idToken)throw new Error('Not authenticated. Run "codevibe login" first.');let s={"Content-Type":"application/json",Authorization:this.tokens.idToken},o=await Z(i.aws.appsyncUrl,{method:"POST",headers:s,body:JSON.stringify({query:e,variables:t})},"AppSync GraphQL request"),a=await o.json();if(o.status===401&&!r&&this.tokens){if(c.info("[AppSyncClient] 401 Unauthorized, refreshing token..."),await this.refreshTokens(this.tokens))return this.graphqlRequest(e,t,!0);throw new Error("Token expired and refresh failed")}if(!o.ok)throw new Error(`GraphQL request failed: ${o.status}`);if(a.errors?.length)throw new Error(`GraphQL error: ${a.errors[0].message}`);return a}async createSession(e){let t={...e,metadata:e.metadata?JSON.stringify(e.metadata):void 0},r=await this.graphqlRequest(C.createSession,{input:t});return c.info("[AppSyncClient] Session created",{sessionId:r.data.createSession.sessionId}),r.data.createSession}async updateSession(e){let t={...e,metadata:e.metadata?JSON.stringify(e.metadata):void 0},r=await this.graphqlRequest(C.updateSession,{input:t});return c.debug("[AppSyncClient] Session updated",{sessionId:r.data.updateSession.sessionId}),r.data.updateSession}async getSession(e){return(await this.graphqlRequest(U.getSession,{sessionId:e})).data.getSession}async createEvent(e){let t={...e,metadata:e.metadata?JSON.stringify(e.metadata):void 0},r=await this.graphqlRequest(C.createEvent,{input:t});return c.debug("[AppSyncClient] Event created",{eventId:r.data.createEvent.eventId,type:r.data.createEvent.type}),r.data.createEvent}async updateEventStatus(e){return(await this.graphqlRequest(C.updateEventStatus,{input:e})).data.updateEventStatus}async listEvents(e,t,r){return(await this.graphqlRequest(U.listEvents,{sessionId:e,source:t,limit:r})).data.listEvents.items}async listSessions(e=100){if(!this.currentUserId)throw new Error("Not authenticated");let t=[],r=null;do{let s=(await this.graphqlRequest(U.listSessions,{userId:this.currentUserId,limit:e,nextToken:r})).data?.listSessions;s?.items&&t.push(...s.items),r=s?.nextToken??null}while(r);return t}async sweepOrphanSessions(e){let t=e.staleThresholdMs??9e5,r=new Set(e.excludeSessionIds??[]),i=Date.now(),s;try{s=await this.listSessions()}catch(a){return c.warn("[AppSyncClient] OrphanSweep: listSessions failed, skipping sweep",{agentType:e.agentType,error:a instanceof Error?a.message:String(a)}),0}let o=0;for(let a of s){if(a.agentType!==e.agentType||a.status!=="ACTIVE"||r.has(a.sessionId)||!a.lastHeartbeatAt)continue;let d=i-new Date(a.lastHeartbeatAt).getTime();if(!(d<t)){c.warn("[AppSyncClient] OrphanSweep: marking stale session INACTIVE",{sessionId:a.sessionId,agentType:a.agentType,lastHeartbeatAt:a.lastHeartbeatAt,heartbeatAgeMinutes:Math.round(d/6e4)});try{await this.updateSession({sessionId:a.sessionId,status:"INACTIVE"}),o++}catch(l){c.warn("[AppSyncClient] OrphanSweep: updateSession failed, leaving row as-is",{sessionId:a.sessionId,error:l instanceof Error?l.message:String(l)})}}}return o>0&&c.info("[AppSyncClient] OrphanSweep complete",{agentType:e.agentType,swept:o}),o}async listUserDeviceKeys(){return(await this.graphqlRequest(U.listUserDeviceKeys,{})).data.listUserDeviceKeys||[]}async registerDeviceKey(e,t,r,i){let s={deviceId:e,publicKey:t,platform:r,deviceName:i};await this.graphqlRequest(C.registerDeviceKey,{input:s}),c.info("[AppSyncClient] Device key registered",{deviceId:e,platform:r})}async grantSessionKey(e){await this.graphqlRequest(C.grantSessionKey,{input:e}),c.info("[AppSyncClient] Session key granted",{sessionId:e.sessionId,deviceId:e.deviceId})}async getAttachmentDownloadUrl(e){return(await this.graphqlRequest(C.getAttachmentDownloadUrl,{s3Key:e})).data.getAttachmentDownloadUrl}subscribeToEvents(e,t,r){c.info("[AppSyncClient] Subscribing to events",{sessionId:e});let i=this.activeSubscriptions.get(e);i&&(this.cleanupSubscriptionState(i),this.activeSubscriptions.delete(e));let s={ws:null,subscriptionId:(0,J.v4)(),sessionId:e,onEvent:t,onError:r,reconnectAttempts:0,isReconnecting:!1,destroyed:!1};return this.activeSubscriptions.set(e,s),this.createSubscription(s),()=>{this.cleanupSubscriptionState(s),this.activeSubscriptions.delete(e)}}buildRealtimeUrl(){let e=w(),t=new URL(e.aws.appsyncUrl),i=/\.appsync-api\.[^.]+\.amazonaws\.com$/.test(t.host)?e.aws.appsyncUrl.replace("https://","wss://").replace("appsync-api","appsync-realtime-api"):`wss://${t.host}/graphql/realtime`,s={host:t.host};this.tokens?.idToken&&(s.Authorization=this.tokens.idToken);let o=Buffer.from(JSON.stringify(s)).toString("base64"),a=Buffer.from(JSON.stringify({})).toString("base64");return`${i}?header=${o}&payload=${a}`}createSubscription(e){let{sessionId:t,subscriptionId:r,onEvent:i,onError:s}=e;try{let o=this.buildRealtimeUrl(),a=new G.default(o,["graphql-ws"]);a.on("open",()=>{c.info("[AppSyncClient] WebSocket connected",{sessionId:t}),a.send(JSON.stringify({type:"connection_init"}))}),a.on("message",d=>{try{let l=JSON.parse(d.toString());switch(l.type){case"connection_ack":this.sendSubscriptionStart(a,e);break;case"start_ack":c.info("[AppSyncClient] Subscription started",{sessionId:t}),e.isReconnecting=!1,e.reconnectAttempts=0,this.startHeartbeat(t);break;case"data":this.resetKeepAliveTimer(e);let g=l.payload?.data?.onEventCreated;g&&g.source==="MOBILE"&&i(g);break;case"ka":this.resetKeepAliveTimer(e);break;case"error":let p=l.payload?.errors?.[0]?.message||"Unknown error";this.handleSubscriptionError(e,new Error(p));break}}catch(l){c.error("[AppSyncClient] Failed to parse message",{error:l})}}),a.on("error",d=>{c.error("[AppSyncClient] WebSocket error",{sessionId:t,error:d.message}),this.handleSubscriptionError(e,d)}),a.on("close",(d,l)=>{c.info("[AppSyncClient] WebSocket closed",{sessionId:t,code:d}),e.keepAliveTimer&&clearTimeout(e.keepAliveTimer),!e.destroyed&&this.activeSubscriptions.get(t)===e&&this.handleSubscriptionError(e,new Error(`WebSocket closed: ${d}`))}),e.ws=a,this.resetKeepAliveTimer(e)}catch(o){this.handleSubscriptionError(e,o)}}sendSubscriptionStart(e,t){let r=w(),{sessionId:i,subscriptionId:s}=t,o={host:new URL(r.aws.appsyncUrl).host};this.tokens?.idToken&&(o.Authorization=this.tokens.idToken),e.send(JSON.stringify({id:s,type:"start",payload:{data:JSON.stringify({query:V.onEventCreated,variables:{sessionId:i}}),extensions:{authorization:o}}}))}resetKeepAliveTimer(e){e.keepAliveTimer&&clearTimeout(e.keepAliveTimer),e.keepAliveTimer=setTimeout(()=>{this.handleSubscriptionError(e,new Error("Keep-alive timeout"))},300*1e3)}handleSubscriptionError(e,t){let{sessionId:r,onError:i}=e;if(e.isReconnecting||!this.activeSubscriptions.has(r))return;e.isReconnecting=!0,e.reconnectAttempts++,this.stopHeartbeat(r);let s=e.reconnectAttempts<=k.urgentMaxAttempts,o;if(s?o=Math.min(k.baseDelayMs*Math.pow(k.backoffMultiplier,e.reconnectAttempts-1),k.maxDelayMs):(o=k.persistentDelayMs,e.reconnectAttempts===k.urgentMaxAttempts+1&&c.info("[AppSyncClient] Switching to persistent reconnect (every 5min)",{sessionId:r})),c.info("[AppSyncClient] Scheduling reconnect",{sessionId:r,attempt:e.reconnectAttempts,phase:s?"urgent":"persistent",delayMs:o}),e.ws){try{e.ws.close(1e3)}catch{}e.ws=null}e.keepAliveTimer&&clearTimeout(e.keepAliveTimer),e.reconnectTimer=setTimeout(async()=>{if(e.isReconnecting=!1,e.destroyed||this.activeSubscriptions.get(r)!==e){c.info("[AppSyncClient] Reconnect skipped \u2014 state is no longer canonical",{sessionId:r});return}try{let a=await y.getTokens(this.environment);a&&(y.isTokenExpired(a)?await this.refreshTokens(a)&&c.info("[AppSyncClient] Tokens refreshed before reconnect",{sessionId:r}):this.tokens=a)}catch{c.warn("[AppSyncClient] Token refresh failed before reconnect, using existing tokens",{sessionId:r})}if(e.destroyed||this.activeSubscriptions.get(r)!==e){c.info("[AppSyncClient] Reconnect skipped after token refresh \u2014 state no longer canonical",{sessionId:r});return}e.subscriptionId=(0,J.v4)(),this.createSubscription(e)},o)}cleanupSubscriptionState(e){if(e.destroyed=!0,e.reconnectTimer&&(clearTimeout(e.reconnectTimer),e.reconnectTimer=void 0),e.keepAliveTimer&&(clearTimeout(e.keepAliveTimer),e.keepAliveTimer=void 0),e.ws){try{e.ws.readyState===G.default.OPEN&&e.ws.send(JSON.stringify({id:e.subscriptionId,type:"stop"}))}catch{}try{e.ws.close(1e3)}catch{}try{e.ws.removeAllListeners()}catch{}e.ws=null}}subscribeToDeviceKeyRegistered(e,t,r,i){c.info("[AppSyncClient] Subscribing to device key registrations",{userId:e}),this.deviceKeyWatcher&&this.stopDeviceKeyWatcherInternal();let s={userId:e,subscriptionId:(0,J.v4)(),ws:null,onNewDevice:t,onReconnect:r,onError:i,reconnectAttempts:0,isReconnecting:!1,destroyed:!1};return this.deviceKeyWatcher=s,this.createDeviceKeyWatcherConnection(s),()=>{this.stopDeviceKeyWatcherInternal()}}stopDeviceKeyWatcher(){this.stopDeviceKeyWatcherInternal()}stopDeviceKeyWatcherInternal(){let e=this.deviceKeyWatcher;if(e){if(e.destroyed=!0,e.reconnectTimer&&(clearTimeout(e.reconnectTimer),e.reconnectTimer=void 0),e.keepAliveTimer&&(clearTimeout(e.keepAliveTimer),e.keepAliveTimer=void 0),e.ws){try{e.ws.readyState===G.default.OPEN&&e.ws.send(JSON.stringify({id:e.subscriptionId,type:"stop"}))}catch{}try{e.ws.close(1e3)}catch{}try{e.ws.removeAllListeners()}catch{}e.ws=null}this.deviceKeyWatcher=null,c.info("[AppSyncClient] Device key watcher stopped")}}createDeviceKeyWatcherConnection(e){try{let t=this.buildRealtimeUrl(),r=new G.default(t,["graphql-ws"]);r.on("open",()=>{c.info("[AppSyncClient] Device key watcher WebSocket connected",{userId:e.userId}),r.send(JSON.stringify({type:"connection_init"}))}),r.on("message",i=>{try{let s=JSON.parse(i.toString());switch(s.type){case"connection_ack":this.sendDeviceKeyWatcherStart(r,e);break;case"start_ack":c.info("[AppSyncClient] Device key watcher subscription started",{userId:e.userId});let o=e.isReconnecting;if(e.isReconnecting=!1,e.reconnectAttempts=0,o&&e.onReconnect)try{e.onReconnect()}catch(l){c.warn("[AppSyncClient] Device key watcher onReconnect handler threw",{error:l})}break;case"data":this.resetDeviceKeyWatcherKeepAlive(e);let a=s.payload?.data?.onDeviceKeyRegistered;if(a){c.info("[AppSyncClient] Device key registration observed",{userId:e.userId,newDeviceId:a.deviceId,platform:a.platform});try{e.onNewDevice(a)}catch(l){c.warn("[AppSyncClient] Device key watcher onNewDevice handler threw",{error:l})}}break;case"ka":this.resetDeviceKeyWatcherKeepAlive(e);break;case"error":let d=s.payload?.errors?.[0]?.message||"Unknown error";this.handleDeviceKeyWatcherError(e,new Error(d));break}}catch(s){c.error("[AppSyncClient] Failed to parse device key watcher message",{error:s})}}),r.on("error",i=>{c.error("[AppSyncClient] Device key watcher WebSocket error",{userId:e.userId,error:i.message}),this.handleDeviceKeyWatcherError(e,i)}),r.on("close",i=>{c.info("[AppSyncClient] Device key watcher WebSocket closed",{userId:e.userId,code:i}),e.keepAliveTimer&&clearTimeout(e.keepAliveTimer),!e.destroyed&&this.deviceKeyWatcher===e&&this.handleDeviceKeyWatcherError(e,new Error(`WebSocket closed: ${i}`))}),e.ws=r,this.resetDeviceKeyWatcherKeepAlive(e)}catch(t){this.handleDeviceKeyWatcherError(e,t)}}sendDeviceKeyWatcherStart(e,t){let r=w(),{userId:i,subscriptionId:s}=t,o={host:new URL(r.aws.appsyncUrl).host};this.tokens?.idToken&&(o.Authorization=this.tokens.idToken),e.send(JSON.stringify({id:s,type:"start",payload:{data:JSON.stringify({query:V.onDeviceKeyRegistered,variables:{userId:i}}),extensions:{authorization:o}}}))}resetDeviceKeyWatcherKeepAlive(e){e.keepAliveTimer&&clearTimeout(e.keepAliveTimer),e.keepAliveTimer=setTimeout(()=>{this.handleDeviceKeyWatcherError(e,new Error("Device key watcher keep-alive timeout"))},300*1e3)}handleDeviceKeyWatcherError(e,t){if(e.isReconnecting||e.destroyed||this.deviceKeyWatcher!==e)return;if(e.isReconnecting=!0,e.reconnectAttempts++,e.onError)try{e.onError(t)}catch{}if(e.ws){try{e.ws.removeAllListeners()}catch{}try{e.ws.close(1e3)}catch{}e.ws=null}e.keepAliveTimer&&(clearTimeout(e.keepAliveTimer),e.keepAliveTimer=void 0);let i=e.reconnectAttempts<=k.urgentMaxAttempts?Math.min(k.baseDelayMs*Math.pow(k.backoffMultiplier,e.reconnectAttempts-1),k.maxDelayMs):k.persistentDelayMs;c.warn("[AppSyncClient] Device key watcher reconnect scheduled",{userId:e.userId,attempts:e.reconnectAttempts,delayMs:i,error:t.message}),e.reconnectTimer=setTimeout(async()=>{if(e.isReconnecting=!1,e.destroyed||this.deviceKeyWatcher!==e){c.info("[AppSyncClient] Device key watcher reconnect skipped \u2014 state no longer canonical",{userId:e.userId});return}try{let s=await y.getTokens(this.environment);s&&(y.isTokenExpired(s)?await this.refreshTokens(s)&&c.info("[AppSyncClient] Tokens refreshed before device key watcher reconnect",{userId:e.userId}):this.tokens=s)}catch{c.warn("[AppSyncClient] Token refresh failed before device key watcher reconnect, using existing tokens",{userId:e.userId})}e.destroyed||this.deviceKeyWatcher!==e||(e.subscriptionId=(0,J.v4)(),this.createDeviceKeyWatcherConnection(e))},i)}watchForMobileEnd(e,t){c.info("[AppSyncClient] Starting mobile-end watcher",{sessionId:e});let r=this.sessionUpdateWatchers.get(e);r&&(c.info("[AppSyncClient] Replacing existing mobile-end watcher",{sessionId:e}),this.cleanupSessionUpdateWatcherState(r),this.sessionUpdateWatchers.delete(e));let i={sessionId:e,subscriptionId:(0,J.v4)(),ws:null,onMobileEndRequested:t,priorStatus:"ACTIVE",firedOnce:!1,reconnectAttempts:0,isReconnecting:!1,destroyed:!1};return this.sessionUpdateWatchers.set(e,i),this.createSessionUpdateWatcherConnection(i),{stop:()=>{this.sessionUpdateWatchers.get(e)===i&&(this.cleanupSessionUpdateWatcherState(i),this.sessionUpdateWatchers.delete(e),c.info("[AppSyncClient] Mobile-end watcher stopped",{sessionId:e}))}}}createSessionUpdateWatcherConnection(e){try{let t=this.buildRealtimeUrl(),r=new G.default(t,["graphql-ws"]);r.on("open",()=>{c.info("[AppSyncClient] Mobile-end watcher WebSocket connected",{sessionId:e.sessionId}),r.send(JSON.stringify({type:"connection_init"}))}),r.on("message",i=>{try{let s=JSON.parse(i.toString());switch(s.type){case"connection_ack":this.sendSessionUpdateWatcherStart(r,e);break;case"start_ack":c.info("[AppSyncClient] Mobile-end watcher subscription started",{sessionId:e.sessionId}),e.isReconnecting=!1,e.reconnectAttempts=0;break;case"data":this.resetSessionUpdateWatcherKeepAlive(e),this.handleSessionUpdatePayload(e,s.payload);break;case"ka":this.resetSessionUpdateWatcherKeepAlive(e);break;case"error":let o=s.payload?.errors?.[0]?.message||"Unknown error";this.handleSessionUpdateWatcherError(e,new Error(o));break}}catch(s){c.error("[AppSyncClient] Failed to parse mobile-end watcher message",{error:s})}}),r.on("error",i=>{c.error("[AppSyncClient] Mobile-end watcher WebSocket error",{sessionId:e.sessionId,error:i.message}),this.handleSessionUpdateWatcherError(e,i)}),r.on("close",i=>{c.info("[AppSyncClient] Mobile-end watcher WebSocket closed",{sessionId:e.sessionId,code:i}),e.keepAliveTimer&&clearTimeout(e.keepAliveTimer),!e.destroyed&&this.sessionUpdateWatchers.get(e.sessionId)===e&&this.handleSessionUpdateWatcherError(e,new Error(`WebSocket closed: ${i}`))}),e.ws=r,this.resetSessionUpdateWatcherKeepAlive(e)}catch(t){this.handleSessionUpdateWatcherError(e,t)}}handleSessionUpdatePayload(e,t){let r=t?.data?.onSessionUpdated;if(!r){c.warn("[AppSyncClient] Mobile-end watcher received malformed payload",{sessionId:e.sessionId});return}if(e.firedOnce)return;let i=r.status;if(i==null){c.debug("[AppSyncClient] Mobile-end watcher skipped non-status payload",{sessionId:e.sessionId});return}if(e.priorStatus==="ACTIVE"&&i==="INACTIVE"){e.firedOnce=!0,e.priorStatus="INACTIVE",c.info("[AppSyncClient] Mobile end requested for session",{sessionId:e.sessionId}),Promise.resolve().then(()=>e.onMobileEndRequested()).catch(s=>{c.warn("[AppSyncClient] Mobile-end callback threw",{sessionId:e.sessionId,error:s})});return}e.priorStatus=i}sendSessionUpdateWatcherStart(e,t){let r=w(),{sessionId:i,subscriptionId:s}=t,o={host:new URL(r.aws.appsyncUrl).host};this.tokens?.idToken&&(o.Authorization=this.tokens.idToken),e.send(JSON.stringify({id:s,type:"start",payload:{data:JSON.stringify({query:V.onSessionUpdated,variables:{sessionId:i}}),extensions:{authorization:o}}}))}resetSessionUpdateWatcherKeepAlive(e){e.keepAliveTimer&&clearTimeout(e.keepAliveTimer),e.keepAliveTimer=setTimeout(()=>{this.handleSessionUpdateWatcherError(e,new Error("Mobile-end watcher keep-alive timeout"))},300*1e3)}handleSessionUpdateWatcherError(e,t){if(e.isReconnecting||e.destroyed||this.sessionUpdateWatchers.get(e.sessionId)!==e)return;if(e.isReconnecting=!0,e.reconnectAttempts++,e.ws){try{e.ws.removeAllListeners()}catch{}try{e.ws.close(1e3)}catch{}e.ws=null}e.keepAliveTimer&&(clearTimeout(e.keepAliveTimer),e.keepAliveTimer=void 0);let i=e.reconnectAttempts<=k.urgentMaxAttempts?Math.min(k.baseDelayMs*Math.pow(k.backoffMultiplier,e.reconnectAttempts-1),k.maxDelayMs):k.persistentDelayMs;c.warn("[AppSyncClient] Mobile-end watcher reconnect scheduled",{sessionId:e.sessionId,attempts:e.reconnectAttempts,delayMs:i,error:t.message}),e.reconnectTimer=setTimeout(async()=>{if(e.isReconnecting=!1,!(e.destroyed||this.sessionUpdateWatchers.get(e.sessionId)!==e)){try{let s=await y.getTokens(this.environment);s&&(y.isTokenExpired(s)?await this.refreshTokens(s):this.tokens=s)}catch{c.warn("[AppSyncClient] Token refresh failed before mobile-end watcher reconnect",{sessionId:e.sessionId})}e.destroyed||this.sessionUpdateWatchers.get(e.sessionId)!==e||(e.subscriptionId=(0,J.v4)(),this.createSessionUpdateWatcherConnection(e))}},i)}cleanupSessionUpdateWatcherState(e){if(e.destroyed=!0,e.reconnectTimer&&(clearTimeout(e.reconnectTimer),e.reconnectTimer=void 0),e.keepAliveTimer&&(clearTimeout(e.keepAliveTimer),e.keepAliveTimer=void 0),e.ws){try{e.ws.readyState===G.default.OPEN&&e.ws.send(JSON.stringify({id:e.subscriptionId,type:"stop"}))}catch{}try{e.ws.close(1e3)}catch{}try{e.ws.removeAllListeners()}catch{}e.ws=null}}startHeartbeat(e,t=120*1e3){this.stopHeartbeat(e),this.sendHeartbeat(e);let r=setInterval(()=>{this.sendHeartbeat(e)},t);this.heartbeatTimers.set(e,r),c.info("[AppSyncClient] Heartbeat started",{sessionId:e,intervalMs:t})}stopHeartbeat(e){let t=this.heartbeatTimers.get(e);t&&(clearInterval(t),this.heartbeatTimers.delete(e),c.info("[AppSyncClient] Heartbeat stopped",{sessionId:e}))}async sendHeartbeat(e){try{await this.updateSession({sessionId:e,lastHeartbeatAt:new Date().toISOString()}),c.debug("[AppSyncClient] Heartbeat sent",{sessionId:e})}catch(t){c.warn("[AppSyncClient] Heartbeat failed",{sessionId:e,error:t})}}cleanupSubscriptions(){this.activeSubscriptions.forEach(e=>{this.cleanupSubscriptionState(e)}),this.activeSubscriptions.clear(),this.stopDeviceKeyWatcherInternal(),this.sessionUpdateWatchers.forEach(e=>{this.cleanupSessionUpdateWatcherState(e)}),this.sessionUpdateWatchers.clear(),this.heartbeatTimers.forEach(e=>clearInterval(e)),this.heartbeatTimers.clear()}};var vt=v(require("crypto")),St=v(require("fs")),Ne=v(require("http")),wt=require("child_process");X();R();B();var Ke=v(require("crypto")),dt=v(require("https")),lt=v(require("os")),qt="G-GS74YEQTB8",Ht="lAfOF6OxRzSQ-NsLBRjhAg",Vt="www.google-analytics.com",Gt=`/mp/collect?measurement_id=${qt}&api_secret=${Ht}`,Jt={port_in_use:"server_start",server_listen_failed:"server_start",browser_open_failed:"browser_open",login_timeout:"awaiting_callback",cognito_rejected:"awaiting_callback",state_mismatch:"awaiting_callback",no_authorization_code:"awaiting_callback",token_exchange_failed:"exchanging_code",token_exchange_network_error:"exchanging_code",keychain_write_failed:"storing_tokens",user_aborted:"unknown",unknown:"unknown"};function jt(){let n=typeof process.getuid=="function"?process.getuid():0;return Ke.createHash("sha256").update(`${lt.hostname()}-${n}`).digest("hex").substring(0,36)}function $(){return{platform:process.platform,source:process.env.CODEVIBE_TELEMETRY_SOURCE||"production"}}async function L(n,e){try{let t=JSON.stringify({client_id:jt(),events:[{name:n,params:e}]});await new Promise(r=>{let i=dt.request({hostname:Vt,path:Gt,method:"POST",headers:{"Content-Type":"application/json"}},()=>r());i.on("error",()=>r()),i.write(t),i.end(),setTimeout(r,2e3)})}catch{}}async function se(n){await L("auth_completed",{...$(),user_id:n})}async function b(n,e){let t={...$(),reason:n,stage:e?.stage??Jt[n]};if(typeof e?.httpStatus=="number"&&(t.http_status=e.httpStatus),e?.errorFragment){let{homedir:r}=await import("os"),i=e.errorFragment.replace(/\x1b\[[0-9;]*[a-zA-Z]/g,"").replace(/\\/g,"/").replace(/[\n\r\t"]/g," ").replace(/[^\x20-\x7E]/g,"").trim(),s=[process.env.HOME,process.env.USERPROFILE,(()=>{try{return r()}catch{return}})()].filter(d=>typeof d=="string"&&d.length>0).map(d=>d.replace(/\\/g,"/"));for(let d of s){let l=d.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");i=i.replace(new RegExp(l,"g"),"~")}i=i.replace(/\/Users\/[^/ ]+/g,"/Users/<user>").replace(/\/home\/[^/ ]+/g,"/home/<user>").replace(/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g,"<email>");let o=i.substring(0,100),a=i.substring(100,200);o&&(t.error_fragment=o),a&&(t.error_fragment_2=a)}await L("auth_failed",t)}var Re=Symbol.for("codevibe.auth.beaconed"),pt=Symbol.for("codevibe.auth.failureReason");function A(n,e){try{Object.defineProperty(n,Re,{value:!0,enumerable:!1,configurable:!0,writable:!1}),Object.defineProperty(n,pt,{value:e,enumerable:!1,configurable:!0,writable:!1})}catch{}return n}function oe(n){return!!(n&&typeof n=="object"&&n[Re])}function Pe(n){if(n&&typeof n=="object"&&n[Re]){let e=n[pt];if(typeof e=="string")return e}}function Q(n){return n<=0?"0":n===1?"1":n<=5?"2-5":"6+"}function me(n){return Ke.createHash("sha256").update(n).digest("hex").slice(0,8)}async function ut(n){return L("session_encryption_device_skipped",{...$(),...n})}async function yt(n){return L("session_encryption_partial_success",{...$(),...n})}async function gt(n){return L("session_encryption_catch_up_grant",{...$(),...n})}async function ht(n){return L("session_encryption_self_rekey_request",{...$(),...n})}async function mt(n){return L("session_encryption_self_rekey_success",{...$(),...n})}async function ft(n){return L("session_encryption_self_rekey_timeout",{...$(),...n})}var ae=8080,kt="/callback",Oe=`http://localhost:${ae}${kt}`,ee=class n{constructor(){}static getInstance(){return n.instance||(n.instance=new n),n.instance}openBrowser(e){console.error(""),console.error("Opening your browser for sign-in..."),this.isRunningInWSL()?console.error("If your browser does not open, paste this URL in your Windows browser:"):console.error("If your browser does not open automatically, visit this URL:"),console.error(` ${e}`),console.error("");let t=this.getBrowserCommands();this.tryBrowserCommand(t,e,0)}getBrowserCommands(){let e=process.platform;if(e==="darwin")return[{cmd:"open",fixedArgs:[]}];if(e==="win32")return[{cmd:"cmd",fixedArgs:["/c","start",""]}];let t=[];return this.isRunningInWSL()&&(t.push({cmd:"wslview",fixedArgs:[]}),t.push({cmd:"cmd.exe",fixedArgs:["/c","start",""]}),t.push({cmd:"powershell.exe",fixedArgs:["-NoProfile","-Command","Start-Process"]})),t.push({cmd:"xdg-open",fixedArgs:[]}),t}isRunningInWSL(){if(process.platform!=="linux")return!1;try{let e=St.readFileSync("/proc/sys/kernel/osrelease","utf8");return/microsoft|wsl/i.test(e)}catch{return!1}}tryBrowserCommand(e,t,r){if(r>=e.length){c.debug("[AuthService] No browser-opening command succeeded. User must open the sign-in URL manually (printed to stderr above)."),console.error(""),console.error("\u26A0\uFE0F Could not open browser automatically."),this.isRunningInWSL()?console.error(" WSL detected \u2014 paste this URL in your Windows browser:"):console.error(" Please copy and paste this URL into your browser:"),console.error(` ${t}`),console.error("");return}let i=e[r],s=[...i.fixedArgs,t],o=!1,a=p=>{o||(o=!0,c.debug(`[AuthService] Browser command '${i.cmd}' ${p}; trying next fallback`),this.tryBrowserCommand(e,t,r+1))},d=p=>{o||(o=!0,c.debug(`[AuthService] Browser command '${i.cmd}' ${p}`))},l;try{l=(0,wt.spawn)(i.cmd,s,{detached:!0,stdio:"ignore"})}catch(p){a(`threw synchronously: ${p?.message||p}`);return}l.on("error",p=>{a(`failed to spawn: ${p?.message||p}`)}),l.on("exit",(p,h)=>{p===0?d("exited successfully"):a(h?`terminated by signal ${h}`:`exited with code ${p}`)}),setTimeout(()=>{d("still running after 3s, assuming success")},3e3).unref(),l.unref()}generateState(){return vt.randomBytes(32).toString("hex")}buildAuthUrl(e){let t=w(),r=new URLSearchParams({client_id:t.aws.cognitoClientId,response_type:"code",scope:"email openid profile",redirect_uri:Oe,state:e});return`https://${t.aws.cognitoDomain}/oauth2/authorize?${r.toString()}`}async exchangeCodeForTokens(e){let t=w(),r=`https://${t.aws.cognitoDomain}/oauth2/token`,i=new URLSearchParams({grant_type:"authorization_code",client_id:t.aws.cognitoClientId,code:e,redirect_uri:Oe}),s;try{s=await Z(r,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:i.toString()},"Token exchange")}catch(a){throw await b("token_exchange_network_error"),A(a,"token_exchange_network_error"),a}if(!s.ok){let a=await s.text(),d=new Error(`Token exchange failed: ${s.status} ${a}`);throw await b("token_exchange_failed",{httpStatus:s.status}),A(d,"token_exchange_failed"),d}let o=await s.json();return{accessToken:o.access_token,idToken:o.id_token,refreshToken:o.refresh_token,expiresIn:o.expires_in}}decodeJwt(e){let t=e.split(".");if(t.length!==3)throw new Error("Invalid JWT");return JSON.parse(Buffer.from(t[1],"base64").toString("utf-8"))}async refreshTokens(e){let t=w(),r=`https://${t.aws.cognitoDomain}/oauth2/token`,i=new URLSearchParams({grant_type:"refresh_token",client_id:t.aws.cognitoClientId,refresh_token:e}),s=await Z(r,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:i.toString()},"Token refresh");if(!s.ok)throw new Error(`Token refresh failed: ${s.status}`);let o=await s.json();return{accessToken:o.access_token,idToken:o.id_token,expiresIn:o.expires_in}}async login(){let e=await y.getTokens(I());if(e&&!y.isTokenExpired(e))return e;let t=this.generateState(),r=this.buildAuthUrl(t);return new Promise((i,s)=>{let o=Ne.createServer(async(a,d)=>{if(!a.url?.startsWith(kt)){d.writeHead(404),d.end("Not found");return}try{let l=new URL(a.url,`http://localhost:${ae}`),g=l.searchParams.get("code"),p=l.searchParams.get("state"),h=l.searchParams.get("error");if(h){let S=new Error(`OAuth error: ${h}`);throw await b("cognito_rejected"),A(S,"cognito_rejected"),S}if(p!==t){let S=new Error("State mismatch");throw await b("state_mismatch"),A(S,"state_mismatch"),S}if(!g){let S=new Error("No authorization code");throw await b("no_authorization_code"),A(S,"no_authorization_code"),S}let f=await this.exchangeCodeForTokens(g),O=this.decodeJwt(f.idToken),m={accessToken:f.accessToken,idToken:f.idToken,refreshToken:f.refreshToken,expiresAt:Date.now()+f.expiresIn*1e3,userId:O.sub,email:O.email||"unknown"};try{await y.setTokens(m,I())}catch(S){throw await b("keychain_write_failed"),A(S,"keychain_write_failed"),S}d.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),d.end(`
206
206
  <!DOCTYPE html>
207
207
  <html>
208
208
  <head><title>Success</title></head>
@@ -211,17 +211,17 @@ ${r.stack}`)):typeof r=="object"?o+=` ${JSON.stringify(r,_t)}`:o+=` ${r}`),o}log
211
211
  <p>You can close this window.</p>
212
212
  </body>
213
213
  </html>
214
- `),setTimeout(()=>{o.close(()=>i(m))},500)}catch(l){let h=String(l?.message||l).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");d.writeHead(400,{"Content-Type":"text/html; charset=utf-8"}),d.end(`
214
+ `),setTimeout(()=>{o.close(()=>i(m))},500)}catch(l){let g=String(l?.message||l).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");d.writeHead(400,{"Content-Type":"text/html; charset=utf-8"}),d.end(`
215
215
  <!DOCTYPE html>
216
216
  <html>
217
217
  <head><title>Error</title></head>
218
218
  <body style="font-family: system-ui; max-width: 720px; margin: 50px auto; padding: 0 16px;">
219
219
  <h1 style="color: #ef4444; text-align: center;">&#10007; Authentication Failed</h1>
220
- <pre style="background: #f4f4f5; padding: 16px; border-radius: 8px; white-space: pre-wrap; word-wrap: break-word; font-size: 13px; line-height: 1.5;">${h}</pre>
220
+ <pre style="background: #f4f4f5; padding: 16px; border-radius: 8px; white-space: pre-wrap; word-wrap: break-word; font-size: 13px; line-height: 1.5;">${g}</pre>
221
221
  <p style="text-align: center; color: #71717a; margin-top: 24px;">You can close this window and try again in your terminal.</p>
222
222
  </body>
223
223
  </html>
224
- `),setTimeout(()=>{o.close(()=>s(l))},500)}});o.on("error",async a=>{if(a.code==="EADDRINUSE"){let d=new Error(`Port ${oe} is in use`);await b("port_in_use"),A(d,"port_in_use"),s(d)}else await b("server_listen_failed"),A(a,"server_listen_failed"),s(a)}),o.listen(oe,"localhost",()=>{c.info("[AuthService] Callback server started"),this.openBrowser(r)}),setTimeout(async()=>{let a=new Error("Login timeout");await b("login_timeout"),A(a,"login_timeout"),o.close(()=>s(a))},120*1e3)})}async logout(){let e=w(),t=await y.deleteTokens(I());return t&&new Promise(r=>{let i=Oe.createServer((s,o)=>{s.url?.startsWith("/signout")?(o.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),o.end(`
224
+ `),setTimeout(()=>{o.close(()=>s(l))},500)}});o.on("error",async a=>{if(a.code==="EADDRINUSE"){let d=new Error(`Port ${ae} is in use`);await b("port_in_use"),A(d,"port_in_use"),s(d)}else await b("server_listen_failed"),A(a,"server_listen_failed"),s(a)}),o.listen(ae,"localhost",()=>{c.info("[AuthService] Callback server started"),this.openBrowser(r)}),setTimeout(async()=>{let a=new Error("Login timeout");await b("login_timeout"),A(a,"login_timeout"),o.close(()=>s(a))},120*1e3)})}async logout(){let e=w(),t=await y.deleteTokens(I());return t&&new Promise(r=>{let i=Ne.createServer((s,o)=>{s.url?.startsWith("/signout")?(o.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),o.end(`
225
225
  <!DOCTYPE html>
226
226
  <html>
227
227
  <head><title>Signed Out</title></head>
@@ -230,27 +230,27 @@ ${r.stack}`)):typeof r=="object"?o+=` ${JSON.stringify(r,_t)}`:o+=` ${r}`),o}log
230
230
  <p>You can close this window.</p>
231
231
  </body>
232
232
  </html>
233
- `),setTimeout(()=>{i.close(()=>r(!0))},500)):(o.writeHead(404),o.end("Not found"))});i.on("error",()=>{r(!0)}),i.listen(oe,"localhost",()=>{let s=`https://${e.aws.cognitoDomain}/logout?client_id=${e.aws.cognitoClientId}&logout_uri=${encodeURIComponent(Pe.replace("/callback","/signout"))}`;this.openBrowser(s)}),setTimeout(()=>{i.close(()=>r(!0))},30*1e3)})}async getStatus(){let e=await y.getTokens(I());return e?{authenticated:!y.isTokenExpired(e),tokens:e}:{authenticated:!1}}},P=Q.getInstance();Y();var u={reset:"\x1B[0m",green:"\x1B[32m",red:"\x1B[31m",yellow:"\x1B[33m",cyan:"\x1B[36m",dim:"\x1B[2m"};async function Jt(){console.log(`${u.cyan}CodeVibe Login${u.reset}
233
+ `),setTimeout(()=>{i.close(()=>r(!0))},500)):(o.writeHead(404),o.end("Not found"))});i.on("error",()=>{r(!0)}),i.listen(ae,"localhost",()=>{let s=`https://${e.aws.cognitoDomain}/logout?client_id=${e.aws.cognitoClientId}&logout_uri=${encodeURIComponent(Oe.replace("/callback","/signout"))}`;this.openBrowser(s)}),setTimeout(()=>{i.close(()=>r(!0))},30*1e3)})}async getStatus(){let e=await y.getTokens(I());return e?{authenticated:!y.isTokenExpired(e),tokens:e}:{authenticated:!1}}},P=ee.getInstance();X();var u={reset:"\x1B[0m",green:"\x1B[32m",red:"\x1B[31m",yellow:"\x1B[33m",cyan:"\x1B[36m",dim:"\x1B[2m"};async function zt(){console.log(`${u.cyan}CodeVibe Login${u.reset}
234
234
  `);try{let n=await P.getStatus();if(n.authenticated&&n.tokens){console.log(`${u.yellow}Already logged in as: ${n.tokens.email}${u.reset}`),console.log(`Token expires: ${new Date(n.tokens.expiresAt).toLocaleString()}`),console.log(`
235
235
  Run '${u.dim}codevibe logout${u.reset}' to sign out first.`),process.exit(0);return}console.log("Opening browser for authentication..."),console.log(`${u.dim}Waiting for callback...${u.reset}
236
236
  `);let e=await P.login();e&&(console.log(`
237
- ${u.green}\u2713 Authentication successful!${u.reset}`),console.log(` User: ${e.email}`),console.log(` User ID: ${e.userId}`),console.log(` Expires: ${new Date(e.expiresAt).toLocaleString()}`),await ie(e.userId)),process.exit(0)}catch(n){let e=(()=>{let t=n?.message;return typeof t=="string"&&t.length>0?t:n==null?"(null/undefined error)":`[no_message ctor=${n?.constructor?.name??typeof n}] ${String(n).substring(0,80)}`})();console.error(`
238
- ${u.red}\u2717 Authentication failed${u.reset}`),console.error(` Error: ${e}`),se(n)||await b("unknown",{errorFragment:e}),process.exit(1)}}async function jt(){console.log(`${u.cyan}CodeVibe Logout${u.reset}
237
+ ${u.green}\u2713 Authentication successful!${u.reset}`),console.log(` User: ${e.email}`),console.log(` User ID: ${e.userId}`),console.log(` Expires: ${new Date(e.expiresAt).toLocaleString()}`),await se(e.userId)),process.exit(0)}catch(n){let e=(()=>{let t=n?.message;return typeof t=="string"&&t.length>0?t:n==null?"(null/undefined error)":`[no_message ctor=${n?.constructor?.name??typeof n}] ${String(n).substring(0,80)}`})();console.error(`
238
+ ${u.red}\u2717 Authentication failed${u.reset}`),console.error(` Error: ${e}`),oe(n)||await b("unknown",{errorFragment:e}),process.exit(1)}}async function Yt(){console.log(`${u.cyan}CodeVibe Logout${u.reset}
239
239
  `);try{let n=await P.getStatus();if(!n.authenticated){console.log(`${u.yellow}Not logged in.${u.reset}`),process.exit(0);return}let e=n.tokens?.email;await P.logout()?(console.log(`${u.green}\u2713 Logged out successfully.${u.reset}`),console.log(` Previous user: ${e}`),console.log(`
240
- ${u.dim}Clearing browser session...${u.reset}`)):console.log(`${u.red}\u2717 Failed to log out.${u.reset}`),process.exit(0)}catch(n){console.error(`${u.red}\u2717 Logout failed: ${n.message}${u.reset}`),process.exit(1)}}async function Gt(){console.log(`${u.cyan}CodeVibe Auth Status${u.reset}
240
+ ${u.dim}Clearing browser session...${u.reset}`)):console.log(`${u.red}\u2717 Failed to log out.${u.reset}`),process.exit(0)}catch(n){console.error(`${u.red}\u2717 Logout failed: ${n.message}${u.reset}`),process.exit(1)}}async function Xt(){console.log(`${u.cyan}CodeVibe Auth Status${u.reset}
241
241
  `);try{let n=await P.getStatus();if(!n.tokens){console.log(`${u.yellow}Not authenticated.${u.reset}`),console.log(`
242
242
  Run '${u.dim}codevibe login${u.reset}' to sign in.`),process.exit(0);return}let e=!n.authenticated;console.log(e?`${u.yellow}\u26A0 Token expired${u.reset}`:`${u.green}\u2713 Authenticated${u.reset}`),console.log(` User: ${n.tokens.email}`),console.log(` User ID: ${n.tokens.userId}`),console.log(` Expires: ${new Date(n.tokens.expiresAt).toLocaleString()}`),e&&console.log(`
243
- ${u.dim}Token will be refreshed automatically.${u.reset}`),process.exit(0)}catch(n){console.error(`${u.red}\u2717 Status check failed: ${n.message}${u.reset}`),process.exit(1)}}async function zt(){console.log(`${u.cyan}CodeVibe Reset Device${u.reset}
243
+ ${u.dim}Token will be refreshed automatically.${u.reset}`),process.exit(0)}catch(n){console.error(`${u.red}\u2717 Status check failed: ${n.message}${u.reset}`),process.exit(1)}}async function Zt(){console.log(`${u.cyan}CodeVibe Reset Device${u.reset}
244
244
  `),console.log(`${u.red}\u26A0 WARNING: This will delete your device identity.${u.reset}`),console.log(`${u.red} Old encrypted sessions will become inaccessible.${u.reset}
245
- `);let{keychainManager:n}=await Promise.resolve().then(()=>(R(),et));try{await n.clearAllData(),console.log(`${u.green}\u2713 Device reset complete.${u.reset}`),console.log(` Run '${u.dim}codevibe login${u.reset}' to set up again.`),process.exit(0)}catch(e){console.error(`${u.red}\u2717 Reset failed: ${e.message}${u.reset}`),process.exit(1)}}function Yt(){console.log(`CodeVibe Authentication
245
+ `);let{keychainManager:n}=await Promise.resolve().then(()=>(R(),nt));try{await n.clearAllData(),console.log(`${u.green}\u2713 Device reset complete.${u.reset}`),console.log(` Run '${u.dim}codevibe login${u.reset}' to set up again.`),process.exit(0)}catch(e){console.error(`${u.red}\u2717 Reset failed: ${e.message}${u.reset}`),process.exit(1)}}function Qt(){console.log(`CodeVibe Authentication
246
246
  `),console.log("Usage:"),console.log(" codevibe login - Sign in via browser"),console.log(" codevibe logout - Sign out"),console.log(" codevibe status - Show auth status"),console.log(" codevibe reset-device - Reset device identity (destructive)"),console.log(`
247
- Environment:`),console.log(' Set ENVIRONMENT env var to "development" or "production" (default)'),console.log(" Example: ENVIRONMENT=development codevibe login")}async function me(n){let e=I();console.log(`${u.dim}Environment: ${e}${u.reset}
248
- `);let r=n.slice(2).filter(i=>!i.startsWith("--"))[0];switch(r){case"login":await Jt();break;case"logout":await jt();break;case"status":await Gt();break;case"reset-device":await zt();break;default:Yt(),process.exit(r?1:0)}}require.main===module&&me(process.argv).catch(n=>{console.error("Error:",n),process.exit(1)});Y();M();var Xt=/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g;function St(n){let e=$e(n);if(!e)return null;let t=Zt(e);if(t)return t;let r=Qt(e);return r||null}function $e(n){return n.replace(/\r/g,`
249
- `).replace(Xt,"").replace(/[│┌┐└┘─├┤┬┴┼╌╎╭╮╯╰║═╔╗╚╝╠╣╦╩╬]/g," ").replace(/[ \t]+\n/g,`
247
+ Environment:`),console.log(' Set ENVIRONMENT env var to "development" or "production" (default)'),console.log(" Example: ENVIRONMENT=development codevibe login")}async function fe(n){let e=I();console.log(`${u.dim}Environment: ${e}${u.reset}
248
+ `);let r=n.slice(2).filter(i=>!i.startsWith("--"))[0];switch(r){case"login":await zt();break;case"logout":await Yt();break;case"status":await Xt();break;case"reset-device":await Zt();break;default:Qt(),process.exit(r?1:0)}}require.main===module&&fe(process.argv).catch(n=>{console.error("Error:",n),process.exit(1)});X();B();var er=/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g;function bt(n){let e=Le(n);if(!e)return null;let t=tr(e);if(t)return t;let r=rr(e);return r||null}function Le(n){return n.replace(/\r/g,`
249
+ `).replace(er,"").replace(/[│┌┐└┘─├┤┬┴┼╌╎╭╮╯╰║═╔╗╚╝╠╣╦╩╬]/g," ").replace(/[ \t]+\n/g,`
250
250
  `).replace(/\n{3,}/g,`
251
251
 
252
- `).trim()}function Zt(n){let e=n.split(`
253
- `).map(h=>h.trim()),t=er(e,h=>/\[(?:y\/n|Y\/n|y\/N)\]/.test(h)),r=t>=0?e[t]:null;if(!r)return null;let i=kt(e,t),s=i.length>0?i.join(`
254
- `):r,o=s.toLowerCase(),a=o.includes("what to change")||o.includes("what should")||o.includes("provide")||o.includes("instructions");return{kind:"yes_no",promptText:s,options:a?[{number:"1",text:"Yes"},{number:"2",text:"No, provide instructions"}]:[{number:"1",text:"Yes"},{number:"2",text:"No"}],submitMap:{1:"y",2:"n"},requiresFollowUpText:a}}function Qt(n){let e=n.split(`
255
- `).map(d=>d.trim()),t=tr(e);if(t.length<2)return null;let r=t.map(({line:d})=>wt(d)).filter(d=>!!d),i={};for(let d of r)i[d.number]=d.number;let s=t[0]?.index??-1,o=kt(e,s-1);return{kind:"numbered",promptText:o.length>0?o.join(`
256
- `):"Select an option",options:r,submitMap:i}}function er(n,e){for(let t=n.length-1;t>=0;t-=1)if(e(n[t]))return t;return-1}function wt(n){let e=n.match(/^(?:[>›❯▸▶➜➤*●]\s*)?(\d+)\.\s+(.*)$/);return e?{number:e[1],text:e[2]}:null}function tr(n){let e=n.map((r,i)=>({index:i,line:r,parsed:wt(r)})).filter(r=>!!r.parsed);if(e.length===0)return[];let t=[e[e.length-1]];for(let r=e.length-2;r>=0;r-=1){let i=e[r],s=t[0];if(i.index!==s.index-1)break;t.unshift(i)}return t.map(({index:r,line:i})=>({index:r,line:i}))}function kt(n,e){if(e<0)return[];let t=Ne(n,e);if(t<0)return[];let{start:r,end:i}=Ue(n,t),s=n.slice(r,i+1).filter(Boolean);if(nr(s)){let p=rr(n,r-1);return p.length>0?p:s}if(r<=1)return s;let o=r-1;if(o=Ne(n,o),o<0||o===r-1)return s;let{start:a,end:d}=Ue(n,o),l=n.slice(a,d+1).filter(Boolean);return l.some(bt)?[...l,...s]:s}function bt(n){return/^(?:would you like to|do you want to|the model would like to|action required|confirm)\b/i.test(n)}function Ne(n,e){let t=e;for(;t>=0&&!n[t];)t-=1;return t}function Ue(n,e){let t=e;for(;t>=0&&n[t];)t-=1;return{start:t+1,end:e}}function rr(n,e){let t=[],r=e;for(;r>=0&&t.length<2&&(r=Ne(n,r),!(r<0));){let{start:s,end:o}=Ue(n,r),a=n.slice(s,o+1).filter(Boolean);a.length>0&&t.unshift(a),r=s-1}if(t.length===0)return[];let i=t.findIndex(s=>s.some(bt));return i>=0?t.slice(i).flat():t[t.length-1]}function nr(n){return n.length===0?!1:n.filter(ir).length>=Math.max(2,Math.ceil(n.length/2))}function ir(n){return/^\d+\s/.test(n)}ee();R();ee();R();M();async function j(n,e,t,r={}){let i;try{i=await t.getSession(n)}catch(p){return c.warn("[SessionRekey] Failed to fetch session state for re-key",{sessionId:n,error:p instanceof Error?p.message:String(p)}),0}if(!i)return c.warn("[SessionRekey] Session not found, skipping re-key",{sessionId:n}),0;if(!i.isEncrypted)return 0;let s=i.encryptedKeys||[],o=new Set(s.map(p=>p.deviceId)),a=r.forceDeviceIds??new Set,d;try{d=await t.listUserDeviceKeys()}catch(p){return c.warn("[SessionRekey] Failed to fetch user device keys",{sessionId:n,error:p instanceof Error?p.message:String(p)}),0}let l=d.filter(p=>!o.has(p.deviceId)||a.has(p.deviceId));if(l.length===0)return 0;c.info("[SessionRekey] Granting session key to devices",{sessionId:n,existingDeviceCount:s.length,grantCount:l.length,grantDeviceIds:l.map(p=>p.deviceId),forceCount:a.size});let h=0;for(let p of l)try{let g=E.encryptSessionKey(e,p.publicKey);await t.grantSessionKey({sessionId:n,deviceId:p.deviceId,encryptedKey:g.encryptedKey,ephemeralPublicKey:g.ephemeralPublicKey}),h++,c.info("[SessionRekey] Granted session key to device",{sessionId:n,deviceId:p.deviceId,platform:p.platform})}catch(g){c.warn("[SessionRekey] Failed to grant session key to device",{sessionId:n,deviceId:p.deviceId,error:g instanceof Error?g.message:String(g)})}return h>0&&c.info("[SessionRekey] Re-key complete",{sessionId:n,grantedCount:h,requestedCount:l.length}),h}async function Et(n,e){let t=e.pollIntervalMs??5e3,r=e.maxAttempts??6,i,s;try{i=await y.getDeviceId(),s=await y.getDevicePrivateKey()}catch(o){c.warn("[SessionRekey] A1 pre-loop keychain read failed",{sessionId:n,error:o instanceof Error?o.message:String(o)});try{e.onTimeout?.(0)}catch{}return null}for(let o=1;o<=r;o++){o>1&&await new Promise(g=>setTimeout(g,t));let a;try{a=await e.appSyncClient.getSession(n)}catch(g){c.warn("[SessionRekey] A1 getSession failed during poll, will retry",{sessionId:n,attempt:o,error:g instanceof Error?g.message:String(g)});continue}let d=a?.encryptedKeys??[],l=d.filter(g=>g.deviceId===i);if(l.length===0){c.info("[SessionRekey] A1 our deviceId still not in encryptedKeys",{sessionId:n,attempt:o,freshDeviceCount:d.length});continue}let h=null,p=[];for(let g=l.length-1;g>=0;g--)try{h=E.decryptSessionKey(l[g],s);break}catch(f){p.push(f instanceof Error?f.message:String(f))}if(h){y.cacheSessionKey(n,h);try{e.onSuccess?.(o)}catch{}return c.info("[SessionRekey] A1 self-rekey successful",{sessionId:n,attempt:o,entriesTriedToDecrypt:l.length}),h}c.warn("[SessionRekey] A1 found entries but all decrypt-failed, will retry",{sessionId:n,attempt:o,entriesTried:l.length,errors:p})}try{e.onTimeout?.(r)}catch{}return c.warn("[SessionRekey] A1 self-rekey exhausted maxAttempts",{sessionId:n,maxAttempts:r}),null}R();async function ae(n,e){try{let t=await y.getDeviceId(),r=await y.getDevicePublicKey(),i=y.getDevicePlatform(),s=y.getDeviceName();e.info("Registering device encryption key",{deviceId:t,platform:i,deviceName:s}),await n.registerDeviceKey(t,r,i,s),y.setIsRegistered(!0),e.info("Device encryption key registered successfully",{deviceId:t})}catch(t){e.warn("Failed to register device encryption key (E2E encryption may not work):",t)}}async function fe(n,e,t){try{let r=await e.listUserDeviceKeys();if(r.length===0)return t.info("No device keys found, session will not be encrypted"),null;t.info("Preparing session encryption",{sessionId:n,deviceCount:r.length});let i=ge(n),{sessionKey:s,encryptedKeys:o,skippedDeviceIds:a}=y.createSessionKey(r,{onDeviceSkipped:d=>{dt({skipped_count_bucket:Z(d),session_hash:i}).catch(()=>{})}});return a.length>0&&lt({session_hash:i,encrypted_count_bucket:Z(o.length),skipped_count_bucket:Z(a.length)}).catch(()=>{}),t.info("Session encryption prepared",{sessionId:n,deviceCount:o.length,skippedCount:a.length}),{sessionKey:s,encryptedKeys:o,skippedDeviceIds:a}}catch(r){return t.warn("Failed to prepare session encryption:",r),null}}async function Le(n,e,t){let{sessionId:r,userId:i,agentType:s,projectPath:o,metadata:a}=n,d=null;try{d=await e.getSession(r)}catch(f){t.warn("Failed to get session (will attempt to create new)",{sessionId:r,error:f})}if(d){t.info("Session exists in backend - reactivating",{sessionId:r,previousStatus:d.status});try{await e.updateSession({sessionId:r,status:"ACTIVE"})}catch(m){t.warn("Failed to reactivate existing session, will continue",{sessionId:r,error:m})}let f=null,O=d.encryptedKeys??[];if(d.isEncrypted){if(O.length>0){try{let m=await y.getSessionKey(r,O);m&&(f=m,y.cacheSessionKey(r,m),t.info("Session key retrieved for resumed session",{sessionId:r}))}catch(m){t.warn("Failed to retrieve session key for resumed session",{sessionId:r,error:m})}if(!f){let m=ge(r);t.info("Self-rekey: re-registering device key + awaiting grant",{sessionId:r,otherDeviceCount:O.length}),ut({session_hash:m,other_device_count_bucket:Z(O.length)}).catch(()=>{});try{await ae(e,t),f=await Et(r,{appSyncClient:e,onSuccess:S=>{yt({session_hash:m,attempt_count:S}).catch(()=>{})},onTimeout:S=>{ht({session_hash:m,attempt_count:S}).catch(()=>{})}})}catch(S){t.warn("Self-rekey path failed",{sessionId:r,error:S instanceof Error?S.message:String(S)})}}}else t.warn("Encrypted session has empty encryptedKeys; cannot self-rekey",{sessionId:r});if(!f){let m=new Error(`Cannot resume encrypted session ${r}: `+(O.length===0?"session is marked encrypted but session.encryptedKeys is empty (corrupt state). Cannot self-rekey without a peer device. Start a new session.":"this device's key is not in session.encryptedKeys and self-rekey did not complete within 30s. This typically means the device key was rotated and mobile has not yet granted access to this device. Open the mobile app to refresh device keys, then retry."));throw m.code="ENCRYPTED_SESSION_NO_KEY",m}}if(f)try{let m=await j(r,f,e);m>0&&(t.info("Session re-keyed for newly registered devices on resume",{sessionId:r,newDeviceCount:m}),pt({session_hash:ge(r),granted_count_bucket:Z(m)}).catch(()=>{}))}catch(m){t.warn("Session re-key on resume failed (non-fatal)",{sessionId:r,error:m instanceof Error?m.message:String(m)})}return{resumed:!0,sessionKey:f}}let l=await fe(r,e,t),h=o,p=a;l&&(h=E.encryptContent(o,l.sessionKey),p&&Object.keys(p).length>0&&(p={encrypted:E.encryptMetadata(p,l.sessionKey)}),t.info("Session data encrypted",{sessionId:r})),t.info("Creating new session in backend",{sessionId:r,userId:i,agentType:s,isEncrypted:!!l}),await e.createSession({sessionId:r,userId:i,agentType:s,projectPath:h,status:"ACTIVE",metadata:p,isEncrypted:l?!0:void 0,creatorDeviceId:l?await y.getDeviceId():void 0,encryptionVersion:l?1:void 0,encryptedKeys:l?.encryptedKeys});let g=l?.sessionKey||null;return l&&y.cacheSessionKey(r,l.sessionKey),t.info("Session created",{sessionId:r,userId:i,isEncrypted:!!l}),{resumed:!1,sessionKey:g}}R();function We(n,e){let t=n.getCurrentUserId(),r=async(s,o)=>{let a=y.getCachedSessionIds();if(a.length===0){e.info("[DeviceKeyWatcher] No active sessions to re-key",{reason:s});return}e.info("[DeviceKeyWatcher] Running re-key pass",{reason:s,activeSessionCount:a.length,forceDeviceCount:o?.size??0});for(let d of a){let l=y.getCachedSessionKey(d);if(l)try{let h=await j(d,l,n,o?{forceDeviceIds:o}:void 0);h>0&&e.info("[DeviceKeyWatcher] Session re-keyed",{sessionId:d,newDeviceCount:h,reason:s})}catch(h){e.warn("[DeviceKeyWatcher] Re-key failed for session (non-fatal)",{sessionId:d,reason:s,error:h instanceof Error?h.message:String(h)})}}},i=n.subscribeToDeviceKeyRegistered(t,s=>{e.info("[DeviceKeyWatcher] New device observed, triggering re-key",{userId:t,newDeviceId:s.deviceId,platform:s.platform,deviceName:s.deviceName}),r(`new-device:${s.deviceId}`,new Set([s.deviceId]))},()=>{r("watcher-reconnect")},s=>{e.warn("[DeviceKeyWatcher] Subscription error (will retry)",{error:s instanceof Error?s.message:String(s)})});return e.info("[DeviceKeyWatcher] Started",{userId:t}),i}0&&(module.exports={AgentType,AppSyncClient,AuthService,CryptoError,CryptoService,DeliveryStatus,ENCRYPTION_VERSION,EventSource,EventType,KeychainError,KeychainManager,Logger,SessionStatus,authService,createLogger,cryptoService,errorWasBeaconed,fireAuthCompletedBeacon,fireAuthFailedBeacon,getConfig,getEnvironment,getErrorReason,keychainManager,loadConfig,logger,markErrorBeaconed,mutations,normalizeSnapshot,parseInteractivePrompt,prepareSessionEncryption,queries,registerDeviceEncryptionKey,rekeySessionForNewDevices,resumeOrCreateSession,runAuthCli,startDeviceKeyWatcher,subscriptions});
252
+ `).trim()}function tr(n){let e=n.split(`
253
+ `).map(g=>g.trim()),t=nr(e,g=>/\[(?:y\/n|Y\/n|y\/N)\]/.test(g)),r=t>=0?e[t]:null;if(!r)return null;let i=It(e,t),s=i.length>0?i.join(`
254
+ `):r,o=s.toLowerCase(),a=o.includes("what to change")||o.includes("what should")||o.includes("provide")||o.includes("instructions");return{kind:"yes_no",promptText:s,options:a?[{number:"1",text:"Yes"},{number:"2",text:"No, provide instructions"}]:[{number:"1",text:"Yes"},{number:"2",text:"No"}],submitMap:{1:"y",2:"n"},requiresFollowUpText:a}}function rr(n){let e=n.split(`
255
+ `).map(d=>d.trim()),t=ir(e);if(t.length<2)return null;let r=t.map(({line:d})=>Et(d)).filter(d=>!!d),i={};for(let d of r)i[d.number]=d.number;let s=t[0]?.index??-1,o=It(e,s-1);return{kind:"numbered",promptText:o.length>0?o.join(`
256
+ `):"Select an option",options:r,submitMap:i}}function nr(n,e){for(let t=n.length-1;t>=0;t-=1)if(e(n[t]))return t;return-1}function Et(n){let e=n.match(/^(?:[>›❯▸▶➜➤*●]\s*)?(\d+)\.\s+(.*)$/);return e?{number:e[1],text:e[2]}:null}function ir(n){let e=n.map((r,i)=>({index:i,line:r,parsed:Et(r)})).filter(r=>!!r.parsed);if(e.length===0)return[];let t=[e[e.length-1]];for(let r=e.length-2;r>=0;r-=1){let i=e[r],s=t[0];if(i.index!==s.index-1)break;t.unshift(i)}return t.map(({index:r,line:i})=>({index:r,line:i}))}function It(n,e){if(e<0)return[];let t=Ue(n,e);if(t<0)return[];let{start:r,end:i}=$e(n,t),s=n.slice(r,i+1).filter(Boolean);if(or(s)){let p=sr(n,r-1);return p.length>0?p:s}if(r<=1)return s;let o=r-1;if(o=Ue(n,o),o<0||o===r-1)return s;let{start:a,end:d}=$e(n,o),l=n.slice(a,d+1).filter(Boolean);return l.some(At)?[...l,...s]:s}function At(n){return/^(?:would you like to|do you want to|the model would like to|action required|confirm)\b/i.test(n)}function Ue(n,e){let t=e;for(;t>=0&&!n[t];)t-=1;return t}function $e(n,e){let t=e;for(;t>=0&&n[t];)t-=1;return{start:t+1,end:e}}function sr(n,e){let t=[],r=e;for(;r>=0&&t.length<2&&(r=Ue(n,r),!(r<0));){let{start:s,end:o}=$e(n,r),a=n.slice(s,o+1).filter(Boolean);a.length>0&&t.unshift(a),r=s-1}if(t.length===0)return[];let i=t.findIndex(s=>s.some(At));return i>=0?t.slice(i).flat():t[t.length-1]}function or(n){return n.length===0?!1:n.filter(ar).length>=Math.max(2,Math.ceil(n.length/2))}function ar(n){return/^\d+\s/.test(n)}te();R();te();R();B();async function j(n,e,t,r={}){let i;try{i=await t.getSession(n)}catch(p){return c.warn("[SessionRekey] Failed to fetch session state for re-key",{sessionId:n,error:p instanceof Error?p.message:String(p)}),0}if(!i)return c.warn("[SessionRekey] Session not found, skipping re-key",{sessionId:n}),0;if(!i.isEncrypted)return 0;let s=i.encryptedKeys||[],o=new Set(s.map(p=>p.deviceId)),a=r.forceDeviceIds??new Set,d;try{d=await t.listUserDeviceKeys()}catch(p){return c.warn("[SessionRekey] Failed to fetch user device keys",{sessionId:n,error:p instanceof Error?p.message:String(p)}),0}let l=d.filter(p=>!o.has(p.deviceId)||a.has(p.deviceId));if(l.length===0)return 0;c.info("[SessionRekey] Granting session key to devices",{sessionId:n,existingDeviceCount:s.length,grantCount:l.length,grantDeviceIds:l.map(p=>p.deviceId),forceCount:a.size});let g=0;for(let p of l)try{let h=E.encryptSessionKey(e,p.publicKey);await t.grantSessionKey({sessionId:n,deviceId:p.deviceId,encryptedKey:h.encryptedKey,ephemeralPublicKey:h.ephemeralPublicKey}),g++,c.info("[SessionRekey] Granted session key to device",{sessionId:n,deviceId:p.deviceId,platform:p.platform})}catch(h){c.warn("[SessionRekey] Failed to grant session key to device",{sessionId:n,deviceId:p.deviceId,error:h instanceof Error?h.message:String(h)})}return g>0&&c.info("[SessionRekey] Re-key complete",{sessionId:n,grantedCount:g,requestedCount:l.length}),g}async function Tt(n,e){let t=e.pollIntervalMs??5e3,r=e.maxAttempts??6,i,s;try{i=await y.getDeviceId(),s=await y.getDevicePrivateKey()}catch(o){c.warn("[SessionRekey] A1 pre-loop keychain read failed",{sessionId:n,error:o instanceof Error?o.message:String(o)});try{e.onTimeout?.(0)}catch{}return null}for(let o=1;o<=r;o++){o>1&&await new Promise(h=>setTimeout(h,t));let a;try{a=await e.appSyncClient.getSession(n)}catch(h){c.warn("[SessionRekey] A1 getSession failed during poll, will retry",{sessionId:n,attempt:o,error:h instanceof Error?h.message:String(h)});continue}let d=a?.encryptedKeys??[],l=d.filter(h=>h.deviceId===i);if(l.length===0){c.info("[SessionRekey] A1 our deviceId still not in encryptedKeys",{sessionId:n,attempt:o,freshDeviceCount:d.length});continue}let g=null,p=[];for(let h=l.length-1;h>=0;h--)try{g=E.decryptSessionKey(l[h],s);break}catch(f){p.push(f instanceof Error?f.message:String(f))}if(g){y.cacheSessionKey(n,g);try{e.onSuccess?.(o)}catch{}return c.info("[SessionRekey] A1 self-rekey successful",{sessionId:n,attempt:o,entriesTriedToDecrypt:l.length}),g}c.warn("[SessionRekey] A1 found entries but all decrypt-failed, will retry",{sessionId:n,attempt:o,entriesTried:l.length,errors:p})}try{e.onTimeout?.(r)}catch{}return c.warn("[SessionRekey] A1 self-rekey exhausted maxAttempts",{sessionId:n,maxAttempts:r}),null}R();async function ce(n,e){try{let t=await y.getDeviceId(),r=await y.getDevicePublicKey(),i=y.getDevicePlatform(),s=y.getDeviceName();e.info("Registering device encryption key",{deviceId:t,platform:i,deviceName:s}),await n.registerDeviceKey(t,r,i,s),y.setIsRegistered(!0),e.info("Device encryption key registered successfully",{deviceId:t})}catch(t){e.warn("Failed to register device encryption key (E2E encryption may not work):",t)}}async function ve(n,e,t){try{let r=await e.listUserDeviceKeys();if(r.length===0)return t.info("No device keys found, session will not be encrypted"),null;t.info("Preparing session encryption",{sessionId:n,deviceCount:r.length});let i=me(n),{sessionKey:s,encryptedKeys:o,skippedDeviceIds:a}=y.createSessionKey(r,{onDeviceSkipped:d=>{ut({skipped_count_bucket:Q(d),session_hash:i}).catch(()=>{})}});return a.length>0&&yt({session_hash:i,encrypted_count_bucket:Q(o.length),skipped_count_bucket:Q(a.length)}).catch(()=>{}),t.info("Session encryption prepared",{sessionId:n,deviceCount:o.length,skippedCount:a.length}),{sessionKey:s,encryptedKeys:o,skippedDeviceIds:a}}catch(r){return t.warn("Failed to prepare session encryption:",r),null}}async function Me(n,e,t){let{sessionId:r,userId:i,agentType:s,projectPath:o,metadata:a}=n,d=null;try{d=await e.getSession(r)}catch(f){t.warn("Failed to get session (will attempt to create new)",{sessionId:r,error:f})}if(d){t.info("Session exists in backend - reactivating",{sessionId:r,previousStatus:d.status});try{await e.updateSession({sessionId:r,status:"ACTIVE"})}catch(m){t.warn("Failed to reactivate existing session, will continue",{sessionId:r,error:m})}let f=null,O=d.encryptedKeys??[];if(d.isEncrypted){if(O.length>0){try{let m=await y.getSessionKey(r,O);m&&(f=m,y.cacheSessionKey(r,m),t.info("Session key retrieved for resumed session",{sessionId:r}))}catch(m){t.warn("Failed to retrieve session key for resumed session",{sessionId:r,error:m})}if(!f){let m=me(r);t.info("Self-rekey: re-registering device key + awaiting grant",{sessionId:r,otherDeviceCount:O.length}),ht({session_hash:m,other_device_count_bucket:Q(O.length)}).catch(()=>{});try{await ce(e,t),f=await Tt(r,{appSyncClient:e,onSuccess:S=>{mt({session_hash:m,attempt_count:S}).catch(()=>{})},onTimeout:S=>{ft({session_hash:m,attempt_count:S}).catch(()=>{})}})}catch(S){t.warn("Self-rekey path failed",{sessionId:r,error:S instanceof Error?S.message:String(S)})}}}else t.warn("Encrypted session has empty encryptedKeys; cannot self-rekey",{sessionId:r});if(!f){let m=new Error(`Cannot resume encrypted session ${r}: `+(O.length===0?"session is marked encrypted but session.encryptedKeys is empty (corrupt state). Cannot self-rekey without a peer device. Start a new session.":"this device's key is not in session.encryptedKeys and self-rekey did not complete within 30s. This typically means the device key was rotated and mobile has not yet granted access to this device. Open the mobile app to refresh device keys, then retry."));throw m.code="ENCRYPTED_SESSION_NO_KEY",m}}if(f)try{let m=await j(r,f,e);m>0&&(t.info("Session re-keyed for newly registered devices on resume",{sessionId:r,newDeviceCount:m}),gt({session_hash:me(r),granted_count_bucket:Q(m)}).catch(()=>{}))}catch(m){t.warn("Session re-key on resume failed (non-fatal)",{sessionId:r,error:m instanceof Error?m.message:String(m)})}return{resumed:!0,sessionKey:f}}let l=await ve(r,e,t),g=o,p=a;l&&(g=E.encryptContent(o,l.sessionKey),p&&Object.keys(p).length>0&&(p={encrypted:E.encryptMetadata(p,l.sessionKey)}),t.info("Session data encrypted",{sessionId:r})),t.info("Creating new session in backend",{sessionId:r,userId:i,agentType:s,isEncrypted:!!l}),await e.createSession({sessionId:r,userId:i,agentType:s,projectPath:g,status:"ACTIVE",metadata:p,isEncrypted:l?!0:void 0,creatorDeviceId:l?await y.getDeviceId():void 0,encryptionVersion:l?1:void 0,encryptedKeys:l?.encryptedKeys});let h=l?.sessionKey||null;return l&&y.cacheSessionKey(r,l.sessionKey),t.info("Session created",{sessionId:r,userId:i,isEncrypted:!!l}),{resumed:!1,sessionKey:h}}R();function We(n,e){let t=n.getCurrentUserId(),r=async(s,o)=>{let a=y.getCachedSessionIds();if(a.length===0){e.info("[DeviceKeyWatcher] No active sessions to re-key",{reason:s});return}e.info("[DeviceKeyWatcher] Running re-key pass",{reason:s,activeSessionCount:a.length,forceDeviceCount:o?.size??0});for(let d of a){let l=y.getCachedSessionKey(d);if(l)try{let g=await j(d,l,n,o?{forceDeviceIds:o}:void 0);g>0&&e.info("[DeviceKeyWatcher] Session re-keyed",{sessionId:d,newDeviceCount:g,reason:s})}catch(g){e.warn("[DeviceKeyWatcher] Re-key failed for session (non-fatal)",{sessionId:d,reason:s,error:g instanceof Error?g.message:String(g)})}}},i=n.subscribeToDeviceKeyRegistered(t,s=>{e.info("[DeviceKeyWatcher] New device observed, triggering re-key",{userId:t,newDeviceId:s.deviceId,platform:s.platform,deviceName:s.deviceName}),r(`new-device:${s.deviceId}`,new Set([s.deviceId]))},()=>{r("watcher-reconnect")},s=>{e.warn("[DeviceKeyWatcher] Subscription error (will retry)",{error:s instanceof Error?s.message:String(s)})});return e.info("[DeviceKeyWatcher] Started",{userId:t}),i}var M=new Map;function Be(n){let e=Date.now(),t=n.agentClock?Date.parse(n.agentClock):NaN,r=Number.isNaN(t)?e:t,i=M.get(n.orderingKey)??0,s=Math.max(r,i+1);if(M.has(n.orderingKey)&&M.delete(n.orderingKey),M.set(n.orderingKey,s),M.size>1024){let o=M.keys().next().value;o!==void 0&&M.delete(o)}return new Date(s).toISOString()}function Fe(){M.clear()}0&&(module.exports={AgentType,AppSyncClient,AuthService,CryptoError,CryptoService,DeliveryStatus,ENCRYPTION_VERSION,EventSource,EventType,KeychainError,KeychainManager,Logger,SessionStatus,_resetPrepareEventTimestampForTesting,authService,createLogger,cryptoService,errorWasBeaconed,fireAuthCompletedBeacon,fireAuthFailedBeacon,getConfig,getEnvironment,getErrorReason,keychainManager,loadConfig,logger,markErrorBeaconed,mutations,normalizeSnapshot,parseInteractivePrompt,prepareEventTimestamp,prepareSessionEncryption,queries,registerDeviceEncryptionKey,rekeySessionForNewDevices,resumeOrCreateSession,runAuthCli,startDeviceKeyWatcher,subscriptions});
@@ -3,3 +3,4 @@ export type { ResumeOrCreateSessionInput, ResumeOrCreateSessionResult } from './
3
3
  export { rekeySessionForNewDevices } from './session-rekey';
4
4
  export { startDeviceKeyWatcher } from './device-key-watcher';
5
5
  export { registerDeviceEncryptionKey } from './register-device-key';
6
+ export { prepareEventTimestamp, _resetPrepareEventTimestampForTesting, } from './prepare-event-timestamp';
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Returns an ISO-8601 timestamp with millisecond precision that is
3
+ * STRICTLY GREATER than any previous timestamp returned for the same
4
+ * `orderingKey` within this process.
5
+ *
6
+ * Formula: `Math.max(baseMs, lastMs + 1)` where:
7
+ * - baseMs = parsed agentClock if provided (and valid), else Date.now()
8
+ * - lastMs = previously-emitted ms for this orderingKey (or 0 if first)
9
+ *
10
+ * `agentClock` ANCHORS the timestamp when provided. This is the load-bearing
11
+ * behavior for agy's transcript replay: agy emits events with their original
12
+ * `created_at` even during a backfill (so subscription clients see them in
13
+ * logical order, not in replay-arrival order). For hook-driven plugins
14
+ * (claude/codex/gemini), `agentClock` is left undefined and baseMs = wallMs.
15
+ *
16
+ * Memory: bounded LRU with cap=MAX_LRU_KEYS. Eviction order is least-
17
+ * recently-touched, so active orderingKeys are never evicted. Resets on
18
+ * process restart (correct: restart is a new lifecycle).
19
+ */
20
+ export declare function prepareEventTimestamp(opts: {
21
+ orderingKey: string;
22
+ agentClock?: string;
23
+ }): string;
24
+ /**
25
+ * Test-only: clear the per-key map. Used by jest/vitest to reset state
26
+ * between tests. Not exported from the package root.
27
+ */
28
+ export declare function _resetPrepareEventTimestampForTesting(): void;
@@ -8,12 +8,19 @@ export declare enum SessionStatus {
8
8
  PAUSED = "PAUSED"
9
9
  }
10
10
  /**
11
- * Agent type for multi-agent support
11
+ * Agent type for multi-agent support.
12
+ *
13
+ * ANTIGRAVITY added 2026-05-19 for the upcoming @quantiya/codevibe-antigravity-plugin
14
+ * shipping before Gemini CLI EOL (2026-06-18). Matches the backend AppSync schema
15
+ * enum already deployed via codevibe-backend-pipeline (commit 2c7ae84). iOS/Android
16
+ * have defensive decoders so unknown raw values degrade to CLAUDE-styled rows on
17
+ * binaries that don't know about the new case yet.
12
18
  */
13
19
  export declare enum AgentType {
14
20
  CLAUDE = "CLAUDE",
15
21
  GEMINI = "GEMINI",
16
- CODEX = "CODEX"
22
+ CODEX = "CODEX",
23
+ ANTIGRAVITY = "ANTIGRAVITY"
17
24
  }
18
25
  /**
19
26
  * Session type
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quantiya/codevibe-core",
3
- "version": "1.0.23",
3
+ "version": "1.0.25",
4
4
  "description": "Core library for CodeVibe plugins - shared keychain, crypto, AppSync, and auth functionality",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -98,6 +98,9 @@ class Range {
98
98
  }
99
99
 
100
100
  parseRange (range) {
101
+ // strip build metadata so it can't bleed into the version
102
+ range = range.replace(BUILDSTRIPRE, '')
103
+
101
104
  // memoize range parsing for performance.
102
105
  // this is a very hot path, and fully deterministic.
103
106
  const memoOpts =
@@ -223,6 +226,7 @@ const debug = require('../internal/debug')
223
226
  const SemVer = require('./semver')
224
227
  const {
225
228
  safeRe: re,
229
+ src,
226
230
  t,
227
231
  comparatorTrimReplace,
228
232
  tildeTrimReplace,
@@ -230,6 +234,9 @@ const {
230
234
  } = require('../internal/re')
231
235
  const { FLAG_INCLUDE_PRERELEASE, FLAG_LOOSE } = require('../internal/constants')
232
236
 
237
+ // unbounded global build-metadata stripper used by parseRange
238
+ const BUILDSTRIPRE = new RegExp(src[t.BUILD], 'g')
239
+
233
240
  const isNullSet = c => c.value === '<0.0.0-0'
234
241
  const isAny = c => c.value === ''
235
242
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "semver",
3
- "version": "7.8.0",
3
+ "version": "7.8.1",
4
4
  "description": "The semantic version parser used by npm.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -174,7 +174,7 @@ const simpleSubset = (sub, dom, options) => {
174
174
  if (higher === c && higher !== gt) {
175
175
  return false
176
176
  }
177
- } else if (gt.operator === '>=' && !satisfies(gt.semver, String(c), options)) {
177
+ } else if (gt.operator === '>=' && !c.test(gt.semver)) {
178
178
  return false
179
179
  }
180
180
  }
@@ -192,7 +192,7 @@ const simpleSubset = (sub, dom, options) => {
192
192
  if (lower === c && lower !== lt) {
193
193
  return false
194
194
  }
195
- } else if (lt.operator === '<=' && !satisfies(lt.semver, String(c), options)) {
195
+ } else if (lt.operator === '<=' && !c.test(lt.semver)) {
196
196
  return false
197
197
  }
198
198
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quantiya/codevibe-claude-plugin",
3
- "version": "1.0.39",
3
+ "version": "1.0.41",
4
4
  "description": "Control Claude Code from your iPhone and Android — real-time sync, approve file edits, send prompts by voice. Part of CodeVibe.",
5
5
  "main": "dist/server.js",
6
6
  "bin": {
@@ -47,7 +47,7 @@
47
47
  "node": ">=18.0.0"
48
48
  },
49
49
  "dependencies": {
50
- "@quantiya/codevibe-core": "^1.0.23",
50
+ "@quantiya/codevibe-core": "^1.0.25",
51
51
  "dotenv": "^16.6.1",
52
52
  "express": "^5.1.0",
53
53
  "graphql": "^16.12.0",