@quantiya/codevibe-codex-plugin 1.0.10 → 1.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/server.js +5 -5
- package/package.json +2 -2
package/dist/server.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";var st=Object.create;var
|
|
1
|
+
"use strict";var st=Object.create;var O=Object.defineProperty;var rt=Object.getOwnPropertyDescriptor;var ot=Object.getOwnPropertyNames;var at=Object.getPrototypeOf,lt=Object.prototype.hasOwnProperty;var pt=(p,t,e,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of ot(t))!lt.call(p,n)&&n!==e&&O(p,n,{get:()=>t[n],enumerable:!(i=rt(t,n))||i.enumerable});return p};var y=(p,t,e)=>(e=p!=null?st(at(p)):{},pt(t||!p||!p.__esModule?O(e,"default",{value:p,enumerable:!0}):e,p));var it=require("uuid"),w=y(require("fs")),x=y(require("path")),nt=y(require("os")),c=require("@quantiya/codevibe-core");var k=y(require("os")),W=y(require("path")),$=require("@quantiya/codevibe-core"),s=(0,$.createLogger)({name:"codevibe-codex",logFile:W.default.join(k.default.tmpdir(),"codevibe-codex-mcp.log"),level:"debug"});var U=require("events"),g=y(require("fs")),C=y(require("path")),K=y(require("readline")),z=require("chokidar"),j=require("@quantiya/codevibe-core");var T=class extends U.EventEmitter{constructor(){super();this.watcher=null;this.filePositions=new Map;this.activeLogFile=null;this.sessionId=null;this.isWatching=!1;this.startTime=0;this.sessionsDir=null}start(){if(this.isWatching){s.warn("Session log watcher already running");return}let e=(0,j.getConfig)().codex.sessionsDir;this.sessionsDir=e,s.info("Starting Codex session log watcher",{sessionsDir:e}),g.existsSync(e)||(s.info("Codex sessions directory does not exist yet, creating...",{sessionsDir:e}),g.mkdirSync(e,{recursive:!0})),this.startTime=Date.now(),this.watcher=(0,z.watch)(e,{persistent:!0,ignoreInitial:!0,awaitWriteFinish:{stabilityThreshold:100,pollInterval:50},depth:4,ignored:i=>{let n=C.basename(i);return g.existsSync(i)&&g.statSync(i).isDirectory()?!1:!n.startsWith("rollout-")||!n.endsWith(".jsonl")}}),this.watcher.on("add",i=>{i.endsWith(".jsonl")&&this.onFileAdded(i)}),this.watcher.on("change",i=>{i.endsWith(".jsonl")&&this.onFileChanged(i)}),this.watcher.on("error",i=>{s.error("Watcher error:",i),this.emit("error",i)}),this.watcher.on("ready",()=>{s.info("Session log watcher ready"),this.bindRecentSessionFile()}),this.isWatching=!0,s.info("Session log watcher started")}stop(){this.watcher&&(this.watcher.close(),this.watcher=null),this.isWatching=!1,this.filePositions.clear(),this.activeLogFile=null,this.sessionId=null,this.sessionsDir=null,s.info("Session log watcher stopped")}getSessionId(){return this.sessionId}getActiveLogFile(){return this.activeLogFile}onFileAdded(e){try{let i=g.statSync(e),n=i.birthtimeMs||i.ctimeMs;if(n<this.startTime-5e3){s.debug("Ignoring old session file",{filePath:e,fileCreatedAt:new Date(n).toISOString(),startTime:new Date(this.startTime).toISOString()});return}}catch(i){s.warn("Could not check file creation time",{filePath:e,error:i})}if(this.activeLogFile){s.debug("Ignoring additional Codex session log for this server instance",{activeLogFile:this.activeLogFile,ignoredFile:e});return}s.info("New Codex session log detected",{filePath:e}),this.activeLogFile=e,this.filePositions.set(e,0),this.readNewLines(e)}onFileChanged(e){if(this.activeLogFile&&e!==this.activeLogFile){s.debug("Ignoring change for non-active session log",{activeLogFile:this.activeLogFile,ignoredFile:e});return}if(!this.activeLogFile){try{let i=g.statSync(e),n=i.birthtimeMs||i.ctimeMs,r=i.mtimeMs;if(n<this.startTime-5e3&&r<this.startTime-5e3){s.debug("Ignoring change for pre-existing session file",{filePath:e,fileCreatedAt:new Date(n).toISOString(),fileModifiedAt:new Date(r).toISOString(),startTime:new Date(this.startTime).toISOString()});return}}catch(i){s.warn("Could not check file creation time during change event",{filePath:e,error:i})}this.activeLogFile=e,this.filePositions.set(e,0)}this.readNewLines(e)}bindRecentSessionFile(){if(!(this.activeLogFile||!this.sessionsDir))try{let i=this.collectRecentSessionFiles(this.sessionsDir).sort((n,r)=>r.modifiedAt-n.modifiedAt)[0];if(!i)return;s.info("Binding recent session file missed during initial watch scan",{filePath:i.filePath,fileCreatedAt:new Date(i.createdAt).toISOString(),fileModifiedAt:new Date(i.modifiedAt).toISOString(),watcherStartTime:new Date(this.startTime).toISOString()}),this.activeLogFile=i.filePath,this.filePositions.set(i.filePath,0),this.readNewLines(i.filePath)}catch(e){s.warn("Failed to backfill recent session file",{error:e})}}collectRecentSessionFiles(e){let i=[],n=[e];for(;n.length>0;){let r=n.pop();if(!r)continue;let o;try{o=g.readdirSync(r,{withFileTypes:!0})}catch{continue}for(let a of o){let l=C.join(r,a.name);if(a.isDirectory()){n.push(l);continue}if(!(!a.isFile()||!a.name.startsWith("rollout-")||!a.name.endsWith(".jsonl")))try{let d=g.statSync(l),u=d.birthtimeMs||d.ctimeMs,h=d.mtimeMs;(u>=this.startTime||h>=this.startTime-5e3)&&i.push({filePath:l,createdAt:u,modifiedAt:h})}catch{}}}return i}async readNewLines(e){let i=this.filePositions.get(e)||0;try{if(g.statSync(e).size<=i)return;let r=g.createReadStream(e,{start:i,encoding:"utf-8"}),o=K.createInterface({input:r,crlfDelay:1/0}),a=i;for await(let l of o)if(a+=Buffer.byteLength(l,"utf-8")+1,!!l.trim())try{let d=JSON.parse(l);this.processLogEntry(d)}catch(d){s.warn("Failed to parse log line",{filePath:e,line:l.substring(0,100),error:d})}this.filePositions.set(e,a)}catch(n){s.error("Error reading log file",{filePath:e,error:n}),this.emit("error",n)}}processLogEntry(e){if(s.debug("Processing log entry",{type:e.type}),e.type==="session_meta"){let i=e.payload;this.sessionId=i.id,s.info("Codex session started",{sessionId:i.id,cwd:i.cwd,cliVersion:i.cli_version}),this.emit("session-started",i);return}this.emit("log-entry",e),e.type==="event_msg"&&e.payload?.type?this.emit(`event:${e.payload.type}`,e):e.type==="response_item"&&e.payload?.type&&this.emit(`response:${e.payload.type}`,e)}};var A=require("uuid"),v=require("@quantiya/codevibe-core");var S=new Map;function B(p,t){let e={sessionId:t,source:v.EventSource.DESKTOP};if(p.type==="event_msg"&&p.payload){let i=p.payload.type;switch(i){case"user_message":return{...e,type:v.EventType.USER_PROMPT,content:p.payload.message||"",metadata:{images:p.payload.images||[]}};case"agent_message":return{...e,type:v.EventType.ASSISTANT_RESPONSE,content:p.payload.message||""};case"agent_reasoning":return{...e,type:v.EventType.REASONING,content:p.payload.text||""};case"token_count":return s.debug("Skipping token_count entry"),null;default:return s.debug("Unknown event_msg type",{type:i}),null}}if(p.type==="response_item"&&p.payload){let i=p.payload.type;if(i==="function_call"){let{name:n,arguments:r,call_id:o}=p.payload,a={};try{a=JSON.parse(r||"{}")}catch{a={raw:r}}let l=(0,A.v4)();S.set(o,{name:n,input:r,eventId:l});let d=D(n),u=ct(n,a);return{...e,type:v.EventType.TOOL_USE,content:u,metadata:{toolName:d,toolInput:a,callId:o,status:"running"}}}if(i==="function_call_output"){let{call_id:n,output:r}=p.payload,o=S.get(n);S.delete(n);let a=o?.name?D(o.name):"Tool",l=ht(r,500);return{...e,type:v.EventType.TOOL_USE,content:`${a} completed:
|
|
2
2
|
${l}`,metadata:{toolName:a,toolOutput:r,callId:n,status:"completed"}}}if(i==="custom_tool_call"){let{name:n,call_id:r,input:o,status:a}=p.payload;S.set(r,{name:n,input:o,eventId:(0,A.v4)()});let l=ut(o),{oldString:d,newString:u}=dt(o),h=l?`Editing: ${l.filePath}`:"Applying patch";return{...e,type:v.EventType.TOOL_USE,content:h,metadata:{tool_name:"Edit",tool_input:{file_path:l?.filePath||"",old_string:d,new_string:u},callId:r,status:a||"running"}}}if(i==="custom_tool_call_output"){let{call_id:n,output:r}=p.payload,o=S.get(n);S.delete(n);let a={};try{a=JSON.parse(r||"{}")}catch{a={raw:r}}let l=a.output?.includes("Success")||!a.error;return{...e,type:v.EventType.TOOL_USE,content:l?"File edit applied successfully":`Edit failed: ${a.error||"Unknown error"}`,metadata:{toolName:"Edit",toolOutput:a,callId:n,status:"completed",success:l}}}return s.debug("Unknown response_item type",{type:i}),null}return p.type==="turn_context"?(s.debug("Skipping turn_context entry"),null):(s.debug("Unhandled log entry type",{type:p.type}),null)}function D(p){return{shell_command:"Bash",shell:"Bash",apply_patch:"Edit",write_file:"Write",read_file:"Read",list_files:"Glob",search_files:"Grep",web_search:"WebSearch",web_fetch:"WebFetch"}[p]||p}function ct(p,t){switch(p){case"shell_command":case"shell":return`Running: ${t.command||"command"}`;case"read_file":return`Reading: ${t.file_path||t.path||"file"}`;case"write_file":return`Writing: ${t.file_path||t.path||"file"}`;case"list_files":return`Listing: ${t.path||"."}`;case"search_files":return`Searching for: ${t.pattern||t.query||"pattern"}`;case"web_search":return`Searching web: ${t.query||"query"}`;default:return`Running ${D(p)}`}}function dt(p){let t=[],e=[],i=/^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/;for(let n of p.split(`
|
|
3
3
|
`))if(!(i.test(n)||n.startsWith("***")||n.startsWith("---")||n.startsWith("+++"))){if(n.startsWith("-"))t.push(n.slice(1));else if(n.startsWith("+"))e.push(n.slice(1));else if(n.startsWith(" ")){let r=n.slice(1);t.push(r),e.push(r)}}return{oldString:t.join(`
|
|
4
4
|
`),newString:e.join(`
|
|
@@ -6,15 +6,15 @@ ${l}`,metadata:{toolName:a,toolOutput:r,callId:n,status:"completed"}}}if(i==="cu
|
|
|
6
6
|
`).slice(-20).join(`
|
|
7
7
|
`);return/\[(?:y\/n|Y\/n|y\/N)\]|^\s*\d+\.\s+/im.test(i)}hashPromptSnapshot(e){let i=e.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g,"").replace(/\r/g,`
|
|
8
8
|
`).replace(/[ \t]+\n/g,`
|
|
9
|
-
`).trim();return(0,Z.createHash)("sha256").update(i).digest("hex")}};var
|
|
9
|
+
`).trim();return(0,Z.createHash)("sha256").update(i).digest("hex")}};var b=require("@quantiya/codevibe-core");var M=class{constructor(){this.sessionState=null;this.unsubscribe=null;this.sessionKey=null;this.pendingInteractivePrompt=null;this.isInitializingSession=!1;this.bufferedLogEntries=[];this.sessionWatcher=new T,this.approvalDetector=new E,this.promptResponder=new _,this.tmuxPaneObserver=new F}async start(){s.info("Starting CodeVibe Codex companion server",{environment:(0,c.getEnvironment)()}),this.appSyncClient=new c.AppSyncClient,await this.appSyncClient.authenticateWithStoredTokens()||(s.error('Authentication failed. Run "codevibe-codex login" first.'),console.error('Not authenticated. Run "codevibe-codex login" to sign in.'),process.exit(1)),s.info("Authenticated successfully",{userId:this.appSyncClient.getCurrentUserId(),email:this.appSyncClient.getCurrentUserEmail()}),await(0,c.registerDeviceEncryptionKey)(this.appSyncClient,s),(0,c.startDeviceKeyWatcher)(this.appSyncClient,s),this.setupEventHandlers(),this.sessionWatcher.start(),s.info("CodeVibe Codex companion server started")}setupEventHandlers(){this.sessionWatcher.on("session-started",async t=>{await this.handleSessionStarted(t)}),this.sessionWatcher.on("log-entry",async t=>{await this.handleLogEntry(t)}),this.approvalDetector.on("approval-pending",async t=>{await this.handleApprovalPending(t)}),this.tmuxPaneObserver.on("prompt-candidate",async t=>{await this.handleTmuxPromptCandidate(t.snapshot)}),this.tmuxPaneObserver.on("observer-error",t=>{s.debug("Tmux pane observer error",{error:t})}),this.sessionWatcher.on("error",t=>{s.error("Session watcher error:",t)})}async handleSessionStarted(t){s.info("Handling new Codex session",{codexSessionId:t.id}),this.isInitializingSession=!0,this.bufferedLogEntries=[],this.sessionState&&await this.endActiveSession("new-codex-session-started");let e=process.env.CODEX_WORKING_DIRECTORY||t.cwd||process.cwd(),i=this.generateSessionId(t.id),n=this.appSyncClient.getCurrentUserId(),r={codexSessionId:t.id,cliVersion:t.cli_version,modelProvider:t.model_provider};try{let o=await(0,c.resumeOrCreateSession)({sessionId:i,userId:n,agentType:c.AgentType.CODEX,projectPath:e,metadata:r},this.appSyncClient,s);this.sessionKey=o.sessionKey}catch(o){throw s.error("Failed to create/resume session:",o),o}try{this.sessionState={sessionId:i,userId:n,projectPath:e,cwd:t.cwd,createdAt:new Date,subscriptionActive:!1,metadata:r,codexSessionId:t.id,codexLogFile:this.sessionWatcher.getActiveLogFile()||void 0},await this.flushBufferedLogEntries(),await this.startTmuxObserver(),this.subscribeToMobileEvents(i),this.appSyncClient.startHeartbeat(i)}catch(o){s.error("Failed to create session:",o),this.bufferedLogEntries=[]}finally{this.isInitializingSession=!1}}async flushBufferedLogEntries(){if(this.bufferedLogEntries.length===0)return;let t=this.bufferedLogEntries;this.bufferedLogEntries=[],s.info("Flushing buffered log entries after session initialization",{count:t.length,sessionId:this.sessionState?.sessionId});for(let e of t)await this.handleLogEntry(e)}async handleLogEntry(t){if(!this.sessionState){if(this.isInitializingSession){this.bufferedLogEntries.push(t),s.debug("Buffering log entry until session initialization completes",{type:t.type,bufferedCount:this.bufferedLogEntries.length});return}s.warn("Received log entry but no active session");return}if(t.type==="response_item"&&t.payload){let i=t.payload.type;i==="function_call"||i==="custom_tool_call"?this.approvalDetector.onToolCallStart(t.payload.call_id,t.payload.name,t.payload.arguments||t.payload.input||""):(i==="function_call_output"||i==="custom_tool_call_output")&&(this.approvalDetector.onToolCallComplete(t.payload.call_id),this.pendingInteractivePrompt?.callId===t.payload.call_id&&(this.pendingInteractivePrompt=null))}let e=B(t,this.sessionState.sessionId);if(e)try{if(this.sessionKey){if(e.content=c.cryptoService.encryptContent(e.content,this.sessionKey),e.metadata){let i=c.cryptoService.encryptMetadata(e.metadata,this.sessionKey);e.metadata={encrypted:i}}e.isEncrypted=!0,s.debug("Event encrypted",{type:e.type})}await this.appSyncClient.createEvent(e),s.debug("Event synced to backend",{type:e.type,encrypted:!!this.sessionKey})}catch(i){s.error("Failed to sync event:",i)}}async handleApprovalPending(t){if(this.sessionState){s.info("Sending approval pending interactive prompt",t);try{let e=await this.tryParseInteractivePromptFromTmux(),i=e?.parsedPrompt??null;if(i&&this.pendingInteractivePrompt&&this.pendingInteractivePrompt.source==="tmux"&&this.pendingInteractivePrompt.promptText===i.promptText){s.debug("Skipping heuristic prompt because tmux prompt is already active",{promptText:i.promptText});return}let n=this.buildToolDetailsForInteractivePrompt(t,e?.snapshot),r=n.tool_name||this.mapToolNameForApproval(t.toolName),o=n.tool_input||this.buildFallbackToolInput(t),a=!!(r&&o),l=this.buildPromptPresentation(i),d=l.options,u=t.filePath?`File: ${t.filePath}`:void 0,h=l.content||`Codex is waiting for approval.
|
|
10
10
|
${t.hint}`;u&&!h.includes(u)&&(h=`${h}
|
|
11
|
-
${u}`),this.pendingInteractivePrompt={promptId:t.callId,callId:t.callId,kind:l.kind,options:d,submitMap:l.submitMap,promptText:l.promptText,createdAt:Date.now(),source:i?"tmux":"heuristic",requiresFollowUpText:l.requiresFollowUpText};let m={isApprovalHint:!0,toolName:t.toolName,toolInput:t.toolInput,hint:t.hint,callId:t.callId,filePath:t.filePath,diff:t.diff,rawInput:t.rawInput,tool_name:r,tool_input:o,has_details:a,options:d,instructions:l.instructions,prompt_source:i?"tmux":"heuristic"},P=!1;s.debug("Interactive prompt (pre-encryption)",{sessionId:this.sessionState.sessionId,callId:t.callId,contentPreview:h.substring(0,200),toolDetails:n,metadata:m}),this.sessionKey&&(h=c.cryptoService.encryptContent(h,this.sessionKey),m={encrypted:c.cryptoService.encryptMetadata(m,this.sessionKey)},P=!0),await this.appSyncClient.createEvent({sessionId:this.sessionState.sessionId,type:c.EventType.INTERACTIVE_PROMPT,source:c.EventSource.DESKTOP,content:h,metadata:m,promptId:t.callId,...P?{isEncrypted:!0}:{}})}catch(e){s.error("Failed to send approval interactive prompt:",e)}}}async handleTmuxPromptCandidate(t){if(!this.sessionState)return;let e=(0,
|
|
11
|
+
${u}`),this.pendingInteractivePrompt={promptId:t.callId,callId:t.callId,kind:l.kind,options:d,submitMap:l.submitMap,promptText:l.promptText,createdAt:Date.now(),source:i?"tmux":"heuristic",requiresFollowUpText:l.requiresFollowUpText};let m={isApprovalHint:!0,toolName:t.toolName,toolInput:t.toolInput,hint:t.hint,callId:t.callId,filePath:t.filePath,diff:t.diff,rawInput:t.rawInput,tool_name:r,tool_input:o,has_details:a,options:d,instructions:l.instructions,prompt_source:i?"tmux":"heuristic"},P=!1;s.debug("Interactive prompt (pre-encryption)",{sessionId:this.sessionState.sessionId,callId:t.callId,contentPreview:h.substring(0,200),toolDetails:n,metadata:m}),this.sessionKey&&(h=c.cryptoService.encryptContent(h,this.sessionKey),m={encrypted:c.cryptoService.encryptMetadata(m,this.sessionKey)},P=!0),await this.appSyncClient.createEvent({sessionId:this.sessionState.sessionId,type:c.EventType.INTERACTIVE_PROMPT,source:c.EventSource.DESKTOP,content:h,metadata:m,promptId:t.callId,...P?{isEncrypted:!0}:{}})}catch(e){s.error("Failed to send approval interactive prompt:",e)}}}async handleTmuxPromptCandidate(t){if(!this.sessionState)return;let e=(0,b.parseInteractivePrompt)(t);if(!e||this.pendingInteractivePrompt&&this.pendingInteractivePrompt.source==="tmux"&&this.pendingInteractivePrompt.promptText===e.promptText)return;let i=this.buildPromptPresentation(e),n=this.getMostRecentPendingToolCall();n||(await new Promise(I=>setTimeout(I,500)),n=this.getMostRecentPendingToolCall());let r=n?this.buildApprovalPromptContextFromPendingCall(n):null,o=r?this.buildToolDetailsForInteractivePrompt(r,t):{},a=o.tool_name||this.mapToolNameForApproval(n?.name),l=o.tool_input||(r?this.buildFallbackToolInput(r):void 0),d=!!(a&&l),u=this.pendingInteractivePrompt?.callId||n?.callId||(0,it.v4)();this.pendingInteractivePrompt={promptId:u,callId:this.pendingInteractivePrompt?.callId||n?.callId,kind:i.kind,options:i.options,submitMap:i.submitMap,promptText:i.promptText,createdAt:Date.now(),source:"tmux",requiresFollowUpText:i.requiresFollowUpText};let h={options:i.options,instructions:i.instructions,prompt_source:"tmux_live",tool_name:a,tool_input:l,has_details:d},m=i.content,P=!1;this.sessionKey&&(m=c.cryptoService.encryptContent(m,this.sessionKey),h={encrypted:c.cryptoService.encryptMetadata(h,this.sessionKey)},P=!0);try{await this.appSyncClient.createEvent({sessionId:this.sessionState.sessionId,type:c.EventType.INTERACTIVE_PROMPT,source:c.EventSource.DESKTOP,content:m,metadata:h,promptId:u,...P?{isEncrypted:!0}:{}}),s.info("Sent tmux-detected interactive prompt",{sessionId:this.sessionState.sessionId,promptText:e.promptText,kind:e.kind})}catch(I){s.error("Failed to send tmux-detected interactive prompt",{error:I})}}async startTmuxObserver(){let t=process.env.CODEVIBE_CODEX_TMUX_SESSION;if(!t){s.debug("Skipping tmux pane observer start - no tmux session in environment");return}try{await this.tmuxPaneObserver.start(t)}catch(e){s.warn("Failed to start tmux pane observer",{tmuxSession:t,error:e})}}async tryParseInteractivePromptFromTmux(){try{let t=await this.tmuxPaneObserver.captureSnapshot(),e=(0,b.parseInteractivePrompt)(t);return s.debug("tmux prompt parse result",{parsed:!!e,kind:e?.kind,promptText:e?.promptText,snapshotPreview:this.summarizePromptSnapshot(t)}),{parsedPrompt:e,snapshot:t}}catch(t){return s.debug("tmux prompt parsing unavailable",{error:t}),null}}buildPromptPresentation(t){return t?{content:t.promptText,promptText:t.promptText,kind:t.kind,options:t.options,submitMap:t.submitMap,instructions:this.buildPromptInstructions(t),requiresFollowUpText:t.requiresFollowUpText}:{content:"Codex is waiting for approval.",promptText:"Codex is waiting for approval.",kind:"yes_no",options:[{number:"1",text:'Yes (sends "y")'},{number:"2",text:'No, tell Codex what to change (sends "n <instructions>")'}],submitMap:{1:"y",2:"n"},instructions:"Reply with 1 to approve, or 2 followed by what to change",requiresFollowUpText:!0}}getMostRecentPendingToolCall(){let t=this.approvalDetector.getPendingCalls();return t.length===0?null:t.reduce((e,i)=>i.timestamp>e.timestamp?i:e)}buildApprovalPromptContextFromPendingCall(t){return{toolName:t.name,filePath:t.filePath,diff:t.diff,toolInput:t.parsedInput,rawInput:t.input,hint:t.filePath?`File: ${t.filePath}`:`Tool: ${this.mapToolNameForApproval(t.name)||t.name}`}}buildPromptInstructions(t){return t.kind==="yes_no"&&t.requiresFollowUpText?"Reply with 1 to approve, or 2 followed by what to change":t.kind==="yes_no"?"Reply with 1 for yes or 2 for no":t.kind==="numbered"?"Reply with the number of the option you want":"Reply with your response"}summarizePromptSnapshot(t){return t.split(`
|
|
12
12
|
`).map(e=>e.trimEnd()).filter(e=>e.length>0).slice(-12).map(e=>e.slice(0,160)).join(`
|
|
13
13
|
`)}translatePromptResponse(t){let e=this.pendingInteractivePrompt;if(!e)return{primaryInput:t};let n=t.trim().match(/^(\d+)(?:[,.:;\-\s]+([\s\S]+))?$/);if(!n)return{primaryInput:t};let r=n[1],o=n[2]?.trim(),a=e.submitMap[r];return a?e.requiresFollowUpText&&o?{primaryInput:a,followUpInput:o}:{primaryInput:a}:{primaryInput:t}}buildToolDetailsForInteractivePrompt(t,e){let i=t.toolName,n=t.toolInput&&typeof t.toolInput=="object"?t.toolInput:void 0;if(i==="apply_patch"){let o=t.diff||t.rawInput;if(o){let{oldString:a,newString:l,oldStartLine:d,newStartLine:u}=this.extractOldNewFromPatch(o),h=e?this.extractDiffLineAnchorsFromSnapshot(e):{};return{tool_name:"Edit",tool_input:{file_path:t.filePath,content:o,diff:t.diff,raw_patch:t.rawInput,old_string:a,new_string:l,old_start_line:d??h.oldStartLine,new_start_line:u??h.newStartLine}}}}if(i==="shell_command"||i==="shell"){let o=n?.command||t.rawInput||t.hint;if(o)return{tool_name:"Bash",tool_input:{command:o,output:n?.output}}}let r={};return t.filePath&&(r.file_path=t.filePath),t.diff&&(r.diff=t.diff),t.rawInput&&(r.raw_input=t.rawInput),Object.keys(r).length>0?{tool_name:i||"Tool",tool_input:r}:{}}buildFallbackToolInput(t){let e={};return t.filePath&&(e.file_path=t.filePath),t.diff&&(e.diff=t.diff),t.rawInput&&(e.raw_input=t.rawInput),t.toolInput&&typeof t.toolInput=="object"&&(e.parsed_input=t.toolInput),Object.keys(e).length>0?e:void 0}mapToolNameForApproval(t){return t?{apply_patch:"Edit",shell_command:"Bash",shell:"Bash"}[t]||t:void 0}extractOldNewFromPatch(t){let e=[],i=[],n=/^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/,r=0,o=0,a=0,l,d;for(let u of t.split(`
|
|
14
14
|
`)){let h=u.match(n);if(h){r+=1,o=Number.parseInt(h[1],10),a=Number.parseInt(h[2],10);continue}if(!(u.startsWith("***")||u.startsWith("---")||u.startsWith("+++")||u.startsWith("*** End Patch"))){if(u.startsWith("-"))l===void 0&&(l=o),e.push(u.slice(1)),o+=1;else if(u.startsWith("+"))d===void 0&&(d=a),i.push(u.slice(1)),a+=1;else if(u.startsWith(" ")){let m=u.slice(1);e.push(m),i.push(m),o+=1,a+=1}}}return{oldString:e.join(`
|
|
15
15
|
`),newString:i.join(`
|
|
16
|
-
`),oldStartLine:r===1?l:void 0,newStartLine:r===1?d:void 0}}extractDiffLineAnchorsFromSnapshot(t){let e=(0,
|
|
17
|
-
`)){let o=r.match(/^\s*(\d+)\s+(.*)$/);if(!o)continue;let a=Number.parseInt(o[1],10),l=o[2];if(Number.isFinite(a)){if(l.startsWith("-")){i??=a;continue}if(l.startsWith("+")){n??=a;continue}i??=a,n??=a}}return s.debug("Recovered diff line anchors from tmux snapshot",{oldStartLine:i,newStartLine:n,snapshotPreview:this.summarizePromptSnapshot(t)}),{oldStartLine:i,newStartLine:n}}subscribeToMobileEvents(t){s.info("Subscribing to mobile events",{sessionId:t}),this.unsubscribe&&this.unsubscribe(),this.unsubscribe=this.appSyncClient.subscribeToEvents(t,async e=>{await this.handleMobileEvent(e)},e=>{s.error("Subscription error:",e)}),this.sessionState&&(this.sessionState.subscriptionActive=!0),s.info("Subscribed to mobile events")}async downloadAttachment(t,e,i){try{let n=t.isEncrypted??i??!1;s.info("Downloading attachment",{id:t.id,type:t.type,filename:t.filename,s3Key:t.s3Key,attachmentIsEncrypted:t.isEncrypted,eventIsEncrypted:i,shouldDecrypt:n});let{downloadUrl:r}=await this.appSyncClient.getAttachmentDownloadUrl(t.s3Key),o=await fetch(r);if(!o.ok)throw new Error(`Failed to download attachment: ${o.status} ${o.statusText}`);let a=Buffer.from(await o.arrayBuffer());if(n&&this.sessionKey)try{s.info("Decrypting attachment",{id:t.id}),a=c.cryptoService.decryptData(a,this.sessionKey),s.info("Attachment decrypted successfully",{id:t.id,decryptedSize:a.length})}catch(m){throw s.error("Failed to decrypt attachment:",{id:t.id,error:m}),new Error("Failed to decrypt attachment")}else n&&!this.sessionKey&&s.warn("Cannot decrypt attachment - no session key available",{id:t.id});let l=x.join(nt.tmpdir(),"codevibe-codex",e);
|
|
16
|
+
`),oldStartLine:r===1?l:void 0,newStartLine:r===1?d:void 0}}extractDiffLineAnchorsFromSnapshot(t){let e=(0,b.normalizeSnapshot)(t),i,n;for(let r of e.split(`
|
|
17
|
+
`)){let o=r.match(/^\s*(\d+)\s+(.*)$/);if(!o)continue;let a=Number.parseInt(o[1],10),l=o[2];if(Number.isFinite(a)){if(l.startsWith("-")){i??=a;continue}if(l.startsWith("+")){n??=a;continue}i??=a,n??=a}}return s.debug("Recovered diff line anchors from tmux snapshot",{oldStartLine:i,newStartLine:n,snapshotPreview:this.summarizePromptSnapshot(t)}),{oldStartLine:i,newStartLine:n}}subscribeToMobileEvents(t){s.info("Subscribing to mobile events",{sessionId:t}),this.unsubscribe&&this.unsubscribe(),this.unsubscribe=this.appSyncClient.subscribeToEvents(t,async e=>{await this.handleMobileEvent(e)},e=>{s.error("Subscription error:",e)}),this.sessionState&&(this.sessionState.subscriptionActive=!0),s.info("Subscribed to mobile events")}async downloadAttachment(t,e,i){try{let n=t.isEncrypted??i??!1;s.info("Downloading attachment",{id:t.id,type:t.type,filename:t.filename,s3Key:t.s3Key,attachmentIsEncrypted:t.isEncrypted,eventIsEncrypted:i,shouldDecrypt:n});let{downloadUrl:r}=await this.appSyncClient.getAttachmentDownloadUrl(t.s3Key),o=await fetch(r);if(!o.ok)throw new Error(`Failed to download attachment: ${o.status} ${o.statusText}`);let a=Buffer.from(await o.arrayBuffer());if(n&&this.sessionKey)try{s.info("Decrypting attachment",{id:t.id}),a=c.cryptoService.decryptData(a,this.sessionKey),s.info("Attachment decrypted successfully",{id:t.id,decryptedSize:a.length})}catch(m){throw s.error("Failed to decrypt attachment:",{id:t.id,error:m}),new Error("Failed to decrypt attachment")}else n&&!this.sessionKey&&s.warn("Cannot decrypt attachment - no session key available",{id:t.id});let l=x.join(nt.tmpdir(),"codevibe-codex",e);w.existsSync(l)||w.mkdirSync(l,{recursive:!0});let d="";if(t.filename){let m=x.extname(t.filename);m&&(d=m)}d||(d={"image/jpeg":".jpg","image/png":".png","image/gif":".gif","image/webp":".webp","image/heic":".heic","application/pdf":".pdf"}[t.type]||".bin");let u=`attachment-${t.id}${d}`,h=x.join(l,u);return w.writeFileSync(h,a),s.info("Attachment saved to temp file",{id:t.id,filePath:h,size:a.length}),h}catch(n){return s.error("Failed to download attachment:",{id:t.id,error:n}),null}}async handleMobileEvent(t){if(t.attachments&&t.attachments.length>0&&s.info("DEBUG: Raw attachment data from subscription",{attachments:JSON.stringify(t.attachments),eventIsEncrypted:t.isEncrypted}),s.info("Received mobile event",{eventId:t.eventId,type:t.type,content:t.content?.substring(0,50),attachmentCount:t.attachments?.length||0,isEncrypted:t.isEncrypted}),!this.sessionState){s.warn("Received mobile event but no active session");return}let e=t.content||"";if(t.isEncrypted&&this.sessionKey)try{e=c.cryptoService.decryptContent(t.content,this.sessionKey),s.debug("Event decrypted successfully",{eventId:t.eventId})}catch(i){s.error("Failed to decrypt event:",{eventId:t.eventId,error:i}),e=t.content}try{await this.appSyncClient.updateEventStatus({eventId:t.eventId,sessionId:t.sessionId,timestamp:t.timestamp,deliveryStatus:c.DeliveryStatus.DELIVERED})}catch(i){s.error("Failed to update delivery status:",i)}if(t.type===c.EventType.USER_PROMPT||t.type===c.EventType.PROMPT_RESPONSE){let i=e,n=t.attachments||[],r=[];if(n.length>0){s.info("Downloading attachments for prompt",{count:n.length});for(let l of n){let d=await this.downloadAttachment(l,this.sessionState.sessionId,t.isEncrypted);d&&r.push(d)}if(r.length>0){let l=r.map(d=>`[Attached file: ${d}]`).join(`
|
|
18
18
|
`);i?i=`${l}
|
|
19
19
|
|
|
20
20
|
${i}`:i=`${l}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quantiya/codevibe-codex-plugin",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.11",
|
|
4
4
|
"description": "Control OpenAI Codex CLI 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.
|
|
50
|
+
"@quantiya/codevibe-core": "^1.0.10",
|
|
51
51
|
"chokidar": "^4.0.0",
|
|
52
52
|
"dotenv": "^16.6.1",
|
|
53
53
|
"express": "^5.1.0",
|