@quantiya/codevibe-codex-plugin 2.0.8 → 2.0.10

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/README.md CHANGED
@@ -48,9 +48,30 @@ install the unified `codevibe --agent` interface yet.
48
48
 
49
49
  ## How it works
50
50
 
51
- Codex CLI writes session logs to `~/.codex/sessions/` as JSONL files. CodeVibe watches those files with chokidar, parses every log entry, and streams it through E2E-encrypted AWS AppSync to your phone.
52
-
53
- Approval prompts (Codex's interactive Y/N confirmations) aren't in the JSONL log, so CodeVibe observes the live tmux pane to detect them — you get real prompts with real options, and your mobile approve/reject is sent back via `tmux send-keys`.
51
+ Codex CLI writes session logs to `~/.codex/sessions/` as JSONL files. A native
52
+ hook supplies the exact rollout path and session id; CodeVibe never guesses log
53
+ ownership from timestamps or working directories, so concurrent sessions in the
54
+ same repository remain isolated. CodeVibe watches that owned log and streams new
55
+ entries through E2E-encrypted AWS AppSync to your phone. Native Codex history is
56
+ never rewritten or deleted. Safety limits keep an unread backlog to its recent
57
+ 64 MiB tail and skip an individual record over 16 MiB instead of risking an
58
+ out-of-memory failure.
59
+
60
+ Approval prompts use Codex's native `PermissionRequest` hook. On macOS and
61
+ qualified native Linux, a live tmux-pane observer is also a fallback for prompt
62
+ formats absent from hooks; mobile approve/reject is sent back via `tmux send-keys`.
63
+ The pane fallback fails closed on WSL until its filesystem boundary is qualified;
64
+ hook-based prompts and ordinary mobile control remain available there.
65
+
66
+ The transient tmux mirror lives in a private random `0700` directory with
67
+ exclusive `0600` data and writer-state files. Its writer independently stops at
68
+ 16 MiB and confirms quiescence before CodeVibe truncates or removes the files.
69
+ If quiescence cannot be confirmed, the linked, bounded files are retained for
70
+ writer-aware cleanup rather than unlinked beneath a live writer. The observer
71
+ will not allocate another mirror while that exact generation is unconfirmed;
72
+ once it reports `done`, the same owner safely reaps it. Completed crash
73
+ leftovers from dead owners are reaped after seven days. The mirror is a prompt
74
+ detector, not conversation storage.
54
75
 
55
76
  Each live Codex process appears as its own session on your phone, so you can run multiple concurrent Codex sessions side-by-side with Claude and Antigravity.
56
77
 
package/dist/server.js CHANGED
@@ -1,30 +1,36 @@
1
- "use strict";var Be=Object.create;var M=Object.defineProperty;var Ke=Object.getOwnPropertyDescriptor;var Ue=Object.getOwnPropertyNames;var $e=Object.getPrototypeOf,ze=Object.prototype.hasOwnProperty;var qe=(c,e)=>{for(var t in e)M(c,t,{get:e[t],enumerable:!0})},re=(c,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of Ue(e))!ze.call(c,s)&&s!==t&&M(c,s,{get:()=>e[s],enumerable:!(i=Ke(e,s))||i.enumerable});return c};var P=(c,e,t)=>(t=c!=null?Be($e(c)):{},re(e||!c||!c.__esModule?M(t,"default",{value:c,enumerable:!0}):t,c)),He=c=>re(M({},"__esModule",{value:!0}),c);var It={};qe(It,{CodexCompanionServer:()=>V,extractShellCommandFromPromptText:()=>Me,extractShellCommandPartsFromPromptText:()=>ie});module.exports=He(It);var Fe=require("uuid"),ee=P(require("crypto")),A=P(require("fs")),N=P(require("path")),De=P(require("os")),te=require("util"),G=require("child_process"),d=require("@quantiya/codevibe-core");var ne=P(require("os")),oe=P(require("path")),ae=require("@quantiya/codevibe-core"),n=(0,ae.createLogger)({name:"codevibe-codex",logFile:oe.default.join(ne.default.tmpdir(),"codevibe-codex-mcp.log"),level:"debug"});var le=require("events"),S=P(require("fs")),_=P(require("path")),pe=require("string_decoder"),ce=require("chokidar"),de=require("@quantiya/codevibe-core");var We=256*1024,L=class extends le.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;this.activeReads=new Map;this.pendingReads=new Set;this.readGeneration=0;this.bindSource=null;this.authoritativeSessionId=null;this.pendingTranscriptBind=null;this.startBaselineSizes=new Map}start(){if(this.isWatching){n.warn("Session log watcher already running");return}let t=(0,de.getConfig)().codex.sessionsDir;this.sessionsDir=t,n.info("Starting Codex session log watcher",{sessionsDir:t}),S.existsSync(t)||(n.info("Codex sessions directory does not exist yet, creating...",{sessionsDir:t}),S.mkdirSync(t,{recursive:!0})),this.startTime=Date.now(),this.readGeneration++,this.bindSource=null,this.authoritativeSessionId=null,this.snapshotStartBaselines(t),this.drainPendingTranscriptBind(),this.watcher=(0,ce.watch)(t,{persistent:!0,ignoreInitial:!0,awaitWriteFinish:{stabilityThreshold:100,pollInterval:50},depth:4,ignored:i=>{let s=_.basename(i);return S.existsSync(i)&&S.statSync(i).isDirectory()?!1:!s.startsWith("rollout-")||!s.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=>{n.error("Watcher error:",i),this.emit("error",i)}),this.watcher.on("ready",()=>{n.info("Session log watcher ready"),this.bindRecentSessionFile()}),this.isWatching=!0,n.info("Session log watcher started")}stop(){this.watcher&&(this.watcher.close(),this.watcher=null),this.isWatching=!1,this.readGeneration++,this.filePositions.clear(),this.activeReads.clear(),this.pendingReads.clear(),this.activeLogFile=null,this.sessionId=null,this.sessionsDir=null,this.bindSource=null,this.authoritativeSessionId=null,this.pendingTranscriptBind=null,this.startBaselineSizes.clear(),n.info("Session log watcher stopped")}getSessionId(){return this.sessionId}getActiveLogFile(){return this.activeLogFile}getBindSource(){return this.bindSource}getAuthoritativeSessionId(){return this.authoritativeSessionId}tryRealpath(t){try{return S.realpathSync(t)}catch{return null}}isInteractiveRollout(t){return!(!t||t.originator==="codex_exec"||t.threadSource==="subagent"||t.sourceSubagent)}computeSeedOffset(t){let i;try{i=S.statSync(t)}catch{return 0}if(this.getSessionAgeMs(t,i)>=this.startTime)return 0;let r=this.startBaselineSizes.get(t);return r!==void 0?r:i.size}applyBind(t,i){this.readGeneration++,this.activeReads.delete(t),this.pendingReads.delete(t),this.activeLogFile=t,this.bindSource=i,i==="heuristic"&&(this.authoritativeSessionId=null),this.filePositions.has(t)||this.filePositions.set(t,this.computeSeedOffset(t)),this.scheduleReadNewLines(t)}bindToFile(t){let i=this.tryRealpath(t);if(!i){n.warn("bindToFile: could not canonicalize path; skipping",{filePath:t});return}if(i===this.activeLogFile){this.bindSource="authoritative";return}n.info("bindToFile: authoritative bind to session rollout",{filePath:i}),this.applyBind(i,"authoritative")}async bindToSessionTranscript(t,i){if(typeof t!="string"||t.length===0)return!1;if(!this.sessionsDir)return this.pendingTranscriptBind={transcriptPath:t,expectedSessionId:i},n.debug("bindToSessionTranscript: watcher not started yet; queued pending authoritative bind",{transcriptPath:t,expectedSessionId:i}),!1;let s=this.tryRealpath(t),r=this.tryRealpath(this.sessionsDir);if(!s||!r)return!1;let o=_.relative(r,s);if(o===".."||o.startsWith(".."+_.sep)||_.isAbsolute(o))return n.warn("bindToSessionTranscript: path not under sessionsDir; rejecting",{transcriptPath:s}),!1;let l=_.basename(s);if(!l.startsWith("rollout-")||!l.endsWith(".jsonl"))return!1;let a=null;for(let p=0;p<3&&(a=this.readSessionMeta(s),!a);p++)await new Promise(u=>setTimeout(u,20));return!a||a.id!==i?(n.debug("bindToSessionTranscript: meta missing or id mismatch; not binding",{transcriptPath:s,metaId:a?.id,expectedSessionId:i}),!1):this.isInteractiveRollout(a)?(this.bindToFile(s),this.authoritativeSessionId=i,!0):(n.debug("bindToSessionTranscript: non-interactive rollout (codex_exec/subagent); not binding",{transcriptPath:s,originator:a.originator,threadSource:a.threadSource}),!1)}async drainPendingTranscriptBind(){let t=this.pendingTranscriptBind;t&&(this.pendingTranscriptBind=null,await this.bindToSessionTranscript(t.transcriptPath,t.expectedSessionId))}onFileAdded(t){let i=this.tryRealpath(t);if(!i)return;try{let r=S.statSync(i);if((r.birthtimeMs||r.ctimeMs)<this.startTime-5e3)return}catch{}if(this.activeLogFile)return;let s=this.readSessionMeta(i);if(!this.isInteractiveRollout(s)){n.debug("Ignoring non-interactive rollout (add)",{filePath:i,originator:s?.originator,threadSource:s?.threadSource});return}n.info("New Codex session log detected (heuristic bind)",{filePath:i}),this.applyBind(i,"heuristic")}onFileChanged(t){let i=this.tryRealpath(t);if(i&&!(this.activeLogFile&&i!==this.activeLogFile)){if(!this.activeLogFile){let s=!1;try{let o=S.statSync(i);s=this.getSessionAgeMs(i,o)>=this.startTime-5e3||o.mtimeMs>=this.startTime-5e3}catch{}if(!s)return;let r=this.readSessionMeta(i);if(!this.isInteractiveRollout(r)){n.debug("Ignoring non-interactive rollout (change)",{filePath:i,originator:r?.originator});return}n.info("Binding session log on change (heuristic)",{filePath:i}),this.applyBind(i,"heuristic");return}this.scheduleReadNewLines(i)}}readSessionMeta(t){let i=null;try{i=S.openSync(t,"r");let s=new pe.StringDecoder("utf8"),r=Buffer.alloc(64*1024),o="",l=0,a=-1;for(;l<We;){let m=S.readSync(i,r,0,r.length,l);if(m<=0||(l+=m,o+=s.write(r.subarray(0,m)),a=o.indexOf(`
2
- `),a!==-1))break}o+=s.end();let p=a===-1?o:o.slice(0,a);if(!p.trim())return null;let u=JSON.parse(p),h=u.payload||{};return{id:typeof h.id=="string"?h.id:void 0,timestamp:typeof u.timestamp=="string"?u.timestamp:void 0,originator:typeof h.originator=="string"?h.originator:void 0,cwd:typeof h.cwd=="string"?h.cwd:void 0,threadSource:typeof h.thread_source=="string"?h.thread_source:void 0,sourceSubagent:!!(h.source&&typeof h.source=="object"&&h.source.subagent)}}catch{return null}finally{if(i!==null)try{S.closeSync(i)}catch{}}}snapshotStartBaselines(t){this.startBaselineSizes.clear();try{for(let{filePath:i,size:s}of this.collectRecentSessionFiles(t,!0)){let r=this.tryRealpath(i);r&&this.startBaselineSizes.set(r,s)}}catch{}}getSessionAgeMs(t,i){let s=this.readSessionMeta(t);if(s?.timestamp){let r=Date.parse(s.timestamp);if(!Number.isNaN(r))return r}return i.birthtimeMs||i.ctimeMs}bindRecentSessionFile(){if(!(this.activeLogFile||!this.sessionsDir))try{let t=this.collectRecentSessionFiles(this.sessionsDir).sort((i,s)=>s.modifiedAt-i.modifiedAt);for(let i of t){let s=this.tryRealpath(i.filePath);if(!s)continue;let r=this.readSessionMeta(s);if(this.isInteractiveRollout(r)){n.info("Binding recent interactive session file missed during initial scan",{filePath:s,watcherStartTime:new Date(this.startTime).toISOString()}),this.applyBind(s,"heuristic");return}}}catch(t){n.warn("Failed to backfill recent session file",{error:t})}}collectRecentSessionFiles(t,i=!1){let s=[],r=[t];for(;r.length>0;){let o=r.pop();if(!o)continue;let l;try{l=S.readdirSync(o,{withFileTypes:!0})}catch{continue}for(let a of l){let p=_.join(o,a.name);if(a.isDirectory()){r.push(p);continue}if(!(!a.isFile()||!a.name.startsWith("rollout-")||!a.name.endsWith(".jsonl")))try{let u=S.statSync(p),h=u.birthtimeMs||u.ctimeMs,m=u.mtimeMs;(i||h>=this.startTime||m>=this.startTime-5e3)&&s.push({filePath:p,createdAt:h,modifiedAt:m,size:u.size})}catch{}}}return s}scheduleReadNewLines(t){let i=this.activeReads.get(t);if(i)return this.pendingReads.add(t),i;let s=this.readGeneration,r=this.drainReadQueue(t,s);return this.activeReads.set(t,r),r.finally(()=>{this.activeReads.get(t)===r&&(this.activeReads.delete(t),this.pendingReads.delete(t))}),r}async drainReadQueue(t,i){do this.pendingReads.delete(t),await this.readNewLines(t,i);while(i===this.readGeneration&&this.pendingReads.has(t))}async readNewLines(t,i){if(i!==this.readGeneration)return;let s=this.filePositions.get(t)||0;try{let r=S.statSync(t);if(r.size<s&&(n.info("Session log truncated/rotated; resetting cursor to 0",{filePath:t,oldPosition:s,size:r.size}),s=0,this.filePositions.set(t,0)),r.size<=s)return;let o=S.createReadStream(t,{start:s,encoding:"utf-8"}),l=s,a="";for await(let p of o){if(i!==this.readGeneration){o.destroy();return}a+=p;let u=a.indexOf(`
3
- `);for(;u!==-1;){if(i!==this.readGeneration){o.destroy();return}let h=a.slice(0,u),m=a.slice(0,u+1);a=a.slice(u+1),l+=Buffer.byteLength(m,"utf-8");let g=h.endsWith("\r")?h.slice(0,-1):h;if(g.trim())try{let f=JSON.parse(g);this.processLogEntry(f)}catch(f){n.warn("Failed to parse log line",{filePath:t,line:g.substring(0,100),error:f})}if(i===this.readGeneration&&this.filePositions.set(t,l),i!==this.readGeneration){o.destroy();return}u=a.indexOf(`
4
- `)}}}catch(r){n.error("Error reading log file",{filePath:t,error:r}),this.emit("error",r)}}processLogEntry(t){if(n.debug("Processing log entry",{type:t.type}),t.type==="session_meta"){let i=t.payload;this.sessionId=i.id,n.info("Codex session started",{sessionId:i.id,cwd:i.cwd,cliVersion:i.cli_version}),this.emit("session-started",i);return}this.emit("log-entry",t),t.type==="event_msg"&&t.payload?.type?this.emit(`event:${t.payload.type}`,t):t.type==="response_item"&&t.payload?.type&&this.emit(`response:${t.payload.type}`,t)}};var j=require("uuid"),E=require("@quantiya/codevibe-core");var R=new Map;function ue(c,e){let t={sessionId:e,source:E.EventSource.DESKTOP};if(c.type==="event_msg"&&c.payload){let i=c.payload.type;switch(i){case"user_message":return{...t,type:E.EventType.USER_PROMPT,content:c.payload.message||"",metadata:{images:c.payload.images||[]}};case"agent_message":return{...t,type:E.EventType.ASSISTANT_RESPONSE,content:c.payload.message||"",metadata:{phase:c.payload.phase}};case"agent_reasoning":return{...t,type:E.EventType.REASONING,content:c.payload.text||""};case"token_count":return n.debug("Skipping token_count entry"),null;default:return n.debug("Unknown event_msg type",{type:i}),null}}if(c.type==="response_item"&&c.payload){let i=c.payload.type;if(i==="function_call"){let{name:s,arguments:r,call_id:o}=c.payload,l={};try{l=JSON.parse(r||"{}")}catch{l={raw:r}}let a=(0,j.v4)();R.set(o,{name:s,input:r,eventId:a});let p=X(s),u=Ve(s,l);return{...t,type:E.EventType.TOOL_USE,content:u,metadata:{toolName:p,toolInput:l,callId:o,status:"running"}}}if(i==="function_call_output"){let{call_id:s,output:r}=c.payload,o=R.get(s);R.delete(s);let l=o?.name?X(o.name):"Tool",a=Xe(r,500);return{...t,type:E.EventType.TOOL_USE,content:`${l} completed:
5
- ${a}`,metadata:{toolName:l,toolOutput:r,callId:s,status:"completed"}}}if(i==="custom_tool_call"){let{name:s,call_id:r,input:o,status:l}=c.payload;R.set(r,{name:s,input:o,eventId:(0,j.v4)()});let a=je(o),{oldString:p,newString:u}=Ge(o),h=a?`Editing: ${a.filePath}`:"Applying patch";return{...t,type:E.EventType.TOOL_USE,content:h,metadata:{tool_name:"Edit",tool_input:{file_path:a?.filePath||"",old_string:p,new_string:u},callId:r,status:l||"running"}}}if(i==="custom_tool_call_output"){let{call_id:s,output:r}=c.payload,o=R.get(s);R.delete(s);let l={};try{l=JSON.parse(r||"{}")}catch{l={raw:r}}let a=l.output?.includes("Success")||!l.error;return{...t,type:E.EventType.TOOL_USE,content:a?"File edit applied successfully":`Edit failed: ${l.error||"Unknown error"}`,metadata:{toolName:"Edit",toolOutput:l,callId:s,status:"completed",success:a}}}return n.debug("Unknown response_item type",{type:i}),null}return c.type==="turn_context"?(n.debug("Skipping turn_context entry"),null):(n.debug("Unhandled log entry type",{type:c.type}),null)}function X(c){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"}[c]||c}function Ve(c,e){switch(c){case"shell_command":case"shell":return`Running: ${e.command||"command"}`;case"read_file":return`Reading: ${e.file_path||e.path||"file"}`;case"write_file":return`Writing: ${e.file_path||e.path||"file"}`;case"list_files":return`Listing: ${e.path||"."}`;case"search_files":return`Searching for: ${e.pattern||e.query||"pattern"}`;case"web_search":return`Searching web: ${e.query||"query"}`;default:return`Running ${X(c)}`}}function Ge(c){let e=[],t=[],i=/^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/;for(let s of c.split(`
6
- `))if(!(i.test(s)||s.startsWith("***")||s.startsWith("---")||s.startsWith("+++"))){if(s.startsWith("-"))e.push(s.slice(1));else if(s.startsWith("+"))t.push(s.slice(1));else if(s.startsWith(" ")){let r=s.slice(1);e.push(r),t.push(r)}}return{oldString:e.join(`
7
- `),newString:t.join(`
8
- `)}}function je(c){if(!c)return null;let e=c.match(/\*\*\* (?:Update|Add|Delete) File: (.+)/);return e?{filePath:e[1].trim()}:null}function Xe(c,e){return c?c.length<=e?c:c.substring(0,e)+"...":""}function he(){R.clear()}var b=require("@quantiya/codevibe-core"),me=3e3,Ye=8,Je=10,Qe=100;function fe(c){let e=Buffer.byteLength(c.carryover);return{status:"partial",progressKey:`${c.fileIdentity.dev}:${c.fileIdentity.ino}:${c.size}:${c.endOfComplete}:${e}`,size:c.size,carryoverBytes:e,endOfComplete:c.endOfComplete}}var Ze="tool_activity",k=class{constructor(e){this.consolidator=null;this.ledger=null;this.sessionMap=null;this.ownership=null;this.cursor=null;this.backendSessionId=null;this.rolloutId=null;this.transcriptPath=null;this.backstopTimer=null;this.backstopInFlight=null;this.cursorReady=Promise.resolve();this.cursorRepaired=!1;this.deps=e}static mintBackendSessionId(){return b.ToolActivitySessionMap.mintCodexBackendSessionId()}async recordedBackendSessionId(e){return new b.ToolActivitySessionMap({agent:"codex",logger:this.deps.logger,lock:new b.InProcessRolloutLock,root:this.deps.root}).ownerBackendSessionId(e)}get sessionId(){return this.backendSessionId}get rollout(){return this.rolloutId}isActive(){return this.consolidator!==null}async start(e,t,i){this.backendSessionId=e,this.rolloutId=t;let s=(0,b.createRolloutLock)({agent:"codex",logger:this.deps.logger,socketDir:this.deps.socketDir,root:this.deps.root});this.sessionMap=new b.ToolActivitySessionMap({agent:"codex",logger:this.deps.logger,lock:s,root:this.deps.root}),this.ownership=await this.sessionMap.claimOwnership(t,e);let r=new b.ToolActivityOutbox({agent:"codex",logger:this.deps.logger,root:this.deps.root});this.ledger=new b.ToolActivityLedger({agent:"codex",sessionId:e,logger:this.deps.logger,root:this.deps.root}),this.consolidator=new b.ToolActivityConsolidator({agent:"codex",sessionId:e,logger:this.deps.logger,outbox:r,ledger:this.ledger,transport:this.buildTransport()}),await this.consolidator.init(),i&&this.bindTranscript(i),this.deps.logger.info("[tool-activity] codex consolidator started",{sessionId:e,rollout:t,hasTranscript:!!i})}bindTranscript(e){if(!this.consolidator||!e||this.transcriptPath===e)return;this.transcriptPath=e;let t=new b.ToolReplayCursor({agent:"codex",sessionId:this.backendSessionId,canonicalPath:e,logger:this.deps.logger,root:this.deps.root,readBytes:this.deps.replayReadBytes,maxFrameBytes:this.deps.replayMaxFrameBytes});this.cursor=t,this.cursorRepaired=!1,this.cursorReady=t.load().catch(s=>this.deps.logger.warn("[tool-activity] codex cursor load failed (rescanning fresh)",{sessionId:this.backendSessionId,err:String(s)}));let i=this.deps.backstopIntervalMs??3e3;i>0&&!this.backstopTimer&&(this.backstopTimer=setInterval(()=>{this.runTranscriptBackstop().catch(s=>this.deps.logger.warn("[tool-activity] codex transcript backstop failed",{sessionId:this.backendSessionId,err:String(s)}))},i),this.backstopTimer.unref?.())}async observeCall(e,t){if(!this.consolidator||!this.sessionMap||!this.ownership||!e.callId||!e.toolName)return;let i=this.consolidator;await this.sessionMap.bindCall(this.ownership,e.callId);let s={id:e.callId,tool:e.toolName,normalizedTarget:st(e.toolName,e.toolInput),ts:e.ts??new Date().toISOString(),byteOffset:e.byteOffset??0,...e.toolInput!==void 0?{digest:(0,b.digestOf)(e.toolInput)}:{}},r=await i.observe(s,t);return r==="closed"?await i.captureTerminal(s,this.deps.shutdownDrainMs??me)?"sealed-terminal":"closed":r}async runTranscriptBackstop(e=!1){await this.runTranscriptBackstopTurn(e)}runTranscriptBackstopTurn(e=!1){if(this.backstopInFlight)return this.backstopInFlight;let t=this.cursor,i=this.cursorReady,s=this.consolidator;if(!s||!t)return Promise.resolve("caught-up");let o=(async()=>{if(await i,this.cursor!==t)return"more";if(!this.cursorRepaired){if(await s.repairLostWindows(t),this.cursor!==t)return"more";this.cursorRepaired=!0}return this.doTranscriptBackstop(t,e)})().finally(()=>{this.backstopInFlight===o&&(this.backstopInFlight=null)});return this.backstopInFlight=o,o}async doTranscriptBackstop(e,t=!1){let i=this.consolidator;if(!i)return"caught-up";this.ledger&&await this.ledger.reloadLegacyOwned();for(let s=0;s<Ye;s+=1){if(this.cursor!==e)return"caught-up";let r=await e.readFrames();if(r.missing)return"caught-up";if(r.frames.length===0)return r.carryover.length>0?fe(r):"caught-up";let o=Number.POSITIVE_INFINITY,l=t?{includeInFlight:!0}:void 0;for(let a of r.frames)for(let p of et(a.line,a.startOffset))await this.observeCall(p,l)==="deferred-in-flight"&&(o=Math.min(o,a.startOffset));if(await i.flush(),o<r.endOfComplete)return await e.checkpoint(o,{fileIdentity:r.fileIdentity,size:r.size,carryover:""}),"more";if(await e.checkpoint(r.endOfComplete,{fileIdentity:r.fileIdentity,size:r.size,carryover:r.carryover}),!r.hasMore)return r.carryover.length>0?fe(r):"caught-up"}return"more"}async flush(){await this.consolidator?.flush()}async stop(){this.backstopTimer&&(clearInterval(this.backstopTimer),this.backstopTimer=null),await this.runTranscriptBackstop().catch(()=>{});let e=Math.max(1,Math.trunc(this.deps.shutdownPartialNoProgressTurns??Qe)),t=null,i=0;for(;;){let s=await this.runTranscriptBackstopTurn(!0);if(s==="caught-up")break;if(typeof s!="string"){if(s.progressKey===t?i+=1:(t=s.progressKey,i=1),i>=e){this.deps.logger.warn("[tool-activity] deferring unchanged incomplete rollout suffix during shutdown; the next owner will re-read it from the durable complete-line boundary",{size:s.size,carryoverBytes:s.carryoverBytes,endOfComplete:s.endOfComplete,unchangedTurns:i});break}await new Promise(r=>setTimeout(r,Je))}else t=null,i=0,await new Promise(r=>setImmediate(r))}await this.consolidator?.drainSends(this.deps.shutdownDrainMs??me).catch(()=>{});try{await this.consolidator?.shutdown()}finally{await this.ownership?.release().catch(()=>{}),this.ownership=null,this.consolidator=null,this.ledger=null,this.sessionMap=null,this.cursor=null}}buildTransport(){let e=async(t,i,s)=>{let r=s?s.sessionKey:this.deps.getSessionKey();if(!r)throw new Error("[tool-activity] no session key at send (retryable)");let o=s?s.sessionKeyGen:this.deps.getSessionKeyGen();await this.deps.createEvent({sessionId:t,type:b.EventType.TOOL_ACTIVITY,source:b.EventSource.DESKTOP,content:this.deps.encryptContent(Ze,r),metadata:{encrypted:this.deps.encryptMetadata({manifest:i.manifest},r)},timestamp:i.windowStart,isEncrypted:!0,clientEventId:i.clientEventId,...o?{expectedSessionKeyGen:o}:{}})};return{send:async(t,i)=>{try{await e(t,i)}catch(s){if((0,b.isSessionKeyStaleError)(s)&&this.deps.refreshSessionKey){let r=await this.deps.refreshSessionKey(t);if(!r)throw s;await e(t,i,r);return}throw s}}}}};function et(c,e){let t;try{t=JSON.parse(c)}catch{return[]}if(t?.type!=="response_item"||!t.payload)return[];let i=t.payload,s=typeof t.timestamp=="string"?t.timestamp:new Date(0).toISOString();if(i.type==="function_call"&&typeof i.call_id=="string"&&typeof i.name=="string"){let r;try{r=JSON.parse(i.arguments||"{}")}catch{r={raw:i.arguments}}return[{callId:i.call_id,toolName:tt(i.name),toolInput:r,ts:s,byteOffset:e}]}if(i.type==="custom_tool_call"&&typeof i.call_id=="string"){let r=it(typeof i.input=="string"?i.input:""),o=r?{file_path:r,patch:i.input}:{patch:i.input};return[{callId:i.call_id,toolName:"Edit",toolInput:o,ts:s,byteOffset:e}]}return[]}function tt(c){return c==="shell"||c==="shell_command"||c==="local_shell"?"Bash":c==="apply_patch"?"Edit":c}function it(c){let e=c.match(/^\*\*\* (?:Update|Add|Delete) File: (.+)$/m);return e?e[1].trim():void 0}function st(c,e){if(!e||typeof e!="object")return;let t=e,i=r=>typeof r=="string"&&r.length>0?r:void 0,s=Array.isArray(t.command)?t.command.filter(r=>typeof r=="string").join(" ").trim().split(/\s+/)[0]:typeof t.command=="string"?t.command.trim().split(/\s+/)[0]:void 0;return i(t.file_path)??i(t.path)??i(t.pattern)??i(t.url)??(s&&s.length>0?s:void 0)}var ge=require("events"),ye=require("@quantiya/codevibe-core");var B=class extends ge.EventEmitter{constructor(){super();this.pendingCalls=new Map;this.timers=new Map;this.timeoutMs=(0,ye.getConfig)().codex.approvalTimeoutMs,n.info("Approval detector initialized",{timeoutMs:this.timeoutMs})}onToolCallStart(t,i,s){n.debug("Tool call started",{callId:t,name:i});let r=this.parseInput(s),o=this.extractFilePath(i,s,r),l=this.extractDiff(i,s,r),a={callId:t,name:i,input:s,filePath:o,diff:l,parsedInput:r,timestamp:Date.now(),notificationSent:!1};if(this.pendingCalls.set(t,a),!this.shouldScheduleApprovalTimeout(i,r)){n.debug("Skipping approval timeout for non-escalated tool call",{callId:t,name:i});return}let p=setTimeout(()=>{this.checkPendingCall(t)},this.timeoutMs);this.timers.set(t,p)}onToolCallComplete(t){n.debug("Tool call completed",{callId:t}),this.pendingCalls.delete(t);let i=this.timers.get(t);i&&(clearTimeout(i),this.timers.delete(t))}checkPendingCall(t){let i=this.pendingCalls.get(t);if(!i||i.notificationSent)return;let s=Date.now()-i.timestamp;n.info("Tool call still pending after timeout",{callId:t,name:i.name,elapsedMs:s}),i.notificationSent=!0,this.pendingCalls.set(t,i),this.emit("approval-pending",{callId:t,toolName:i.name,hint:this.extractHint(i.name,i.input,i.filePath),filePath:i.filePath,diff:i.diff,toolInput:i.parsedInput,rawInput:i.input,elapsedMs:s})}extractHint(t,i,s){if(s)return`File: ${s}`;if(t==="apply_patch"&&i){let r=i.match(/\*\*\* (?:Update|Add|Delete) File: (.+)/);if(r)return`File: ${r[1].trim()}`}if(t==="exec_command"||t==="shell_command"||t==="shell")try{let r=JSON.parse(i),o=typeof r.command=="string"?r.command:typeof r.cmd=="string"?r.cmd:void 0;if(o)return`Command: ${o.substring(0,50)}${o.length>50?"...":""}`}catch{}return`Tool: ${this.mapToolName(t)}`}mapToolName(t){return{exec_command:"Bash",shell_command:"Bash",shell:"Bash",apply_patch:"File Edit",write_file:"Write File",read_file:"Read File"}[t]||t}parseInput(t){if(t)try{return JSON.parse(t)}catch{return}}shouldScheduleApprovalTimeout(t,i){return t!=="exec_command"&&t!=="shell_command"&&t!=="shell"?!1:i?.sandbox_permissions==="require_escalated"}extractFilePath(t,i,s){if(t==="apply_patch"&&i){let o=i.match(/\*\*\* (?:Update|Add|Delete) File: (.+)/);if(o)return o[1].trim()}let r=s?.file_path||s?.path||s?.filePath;if(r&&typeof r=="string")return r}extractDiff(t,i,s){if(t==="apply_patch"&&i)return i;if(s?.diff&&typeof s.diff=="string")return s.diff}getPendingCalls(){return Array.from(this.pendingCalls.values())}hasPendingCalls(){return this.pendingCalls.size>0}clear(){for(let t of this.timers.values())clearTimeout(t);this.timers.clear(),this.pendingCalls.clear(),n.debug("Approval detector cleared")}shutdown(){this.clear(),this.removeAllListeners(),n.info("Approval detector shutdown")}};var ve=require("child_process"),Se=require("util");var Y=(0,Se.promisify)(ve.exec),F="__CODEVIBE_CODEX_KEY_ESCAPE__",K=class{async sendInput(e,t){n.info("Attempting to send input to Codex",{sessionId:e,input:t});try{let i=process.env.CODEVIBE_CODEX_TMUX_SESSION;return i?(n.info("Using tmux send-keys",{tmuxSession:i}),t===F?await this.sendKeyViaTmux(i,"Escape"):await this.sendViaTmux(i,t),n.info("Successfully sent input to Codex",{sessionId:e,input:t}),!0):(n.error("No tmux session found - CodeVibe Companion launch is required",{sessionId:e,hint:"Start Codex CLI using `codevibe --agent codex`"}),!1)}catch(i){return n.error("Failed to send input to Codex",{sessionId:e,error:i instanceof Error?i.message:String(i)}),!1}}async sendViaTmux(e,t){let i=t.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\$/g,"\\$").replace(/`/g,"\\`");n.info("Sending via tmux",{sessionName:e,inputLength:t.length});try{let s=`tmux send-keys -t "${e}" -l "${i}"`;await Y(s),await this.delay(500);let r=`tmux send-keys -t "${e}" Enter`;await Y(r),n.info("tmux send-keys completed")}catch(s){throw n.error("tmux send-keys failed",{sessionName:e,error:s}),s}}async sendKeyViaTmux(e,t){n.info("Sending special key via tmux",{sessionName:e,key:t});try{let i=`tmux send-keys -t "${e}" ${t}`;await Y(i),n.info("tmux special-key send completed")}catch(i){throw n.error("tmux special-key send failed",{sessionName:e,key:t,error:i}),i}}delay(e){return new Promise(t=>setTimeout(t,e))}isApprovalResponse(e){let t=e.trim().toLowerCase();return["y","n","a","q","e","yes","no"].includes(t)||/^[0-9]+$/.test(t)}};var I=P(require("fs")),be=P(require("os")),$=P(require("path")),Pe=require("crypto"),Ie=require("child_process"),we=require("events"),_e=require("util");var J=(0,_e.promisify)(Ie.exec),U=class extends we.EventEmitter{constructor(){super(...arguments);this.sessionName=null;this.started=!1;this.pipeFilePath=null;this.filePosition=0;this.watcher=null;this.processing=!1;this.pendingRead=!1;this.lastPromptHash=null}async start(t){if(this.started&&this.sessionName===t){n.debug("Tmux pane observer already started",{sessionName:t});return}this.started&&await this.stop(),this.sessionName=t,this.started=!0,this.filePosition=0,this.lastPromptHash=null,this.pipeFilePath=$.join(be.tmpdir(),`codevibe-codex-pane-${process.pid}.log`),I.mkdirSync($.dirname(this.pipeFilePath),{recursive:!0}),I.writeFileSync(this.pipeFilePath,""),await this.enablePipePane(),this.startFileWatcher(),n.info("Tmux pane observer started",{sessionName:t})}async stop(){if(this.started){try{await this.disablePipePane()}catch(t){n.debug("Failed to disable tmux pipe-pane cleanly",{error:t})}if(this.watcher&&(this.watcher.close(),this.watcher=null),this.pipeFilePath)try{I.unlinkSync(this.pipeFilePath)}catch{}n.info("Tmux pane observer stopped",{sessionName:this.sessionName}),this.started=!1,this.sessionName=null,this.pipeFilePath=null,this.filePosition=0,this.processing=!1,this.pendingRead=!1,this.lastPromptHash=null,this.removeAllListeners("prompt-candidate"),this.removeAllListeners("observer-error")}}resetLastPromptHash(){this.lastPromptHash=null}async captureSnapshot(t=120){if(!this.sessionName)throw new Error("Tmux pane observer is not started");let i=Math.max(1,Math.floor(t)),s=this.escapeShellArg(this.sessionName),r=`tmux capture-pane -p -e -J -S -${i} -t '${s}'`;try{let{stdout:o}=await J(r);return o}catch(o){throw n.error("Failed to capture tmux pane snapshot",{sessionName:this.sessionName,error:o}),this.emit("observer-error",o),o}}escapeShellArg(t){return t.replace(/'/g,"'\\''")}async enablePipePane(){if(!this.sessionName||!this.pipeFilePath)throw new Error("Tmux pane observer is not initialized");let t=this.escapeShellArg(this.sessionName),i=this.escapeShellArg(this.pipeFilePath),s=`tmux pipe-pane -O -t '${t}' "cat >> '${i}'"`;await J(s),n.debug("Enabled tmux pipe-pane mirroring",{sessionName:this.sessionName,pipeFilePath:this.pipeFilePath})}async disablePipePane(){if(!this.sessionName)return;let i=`tmux pipe-pane -t '${this.escapeShellArg(this.sessionName)}'`;await J(i)}startFileWatcher(){this.pipeFilePath&&(this.watcher=I.watch(this.pipeFilePath,t=>{t==="change"&&this.processFileChanges()}))}async processFileChanges(){if(this.pipeFilePath){if(this.processing){this.pendingRead=!0;return}this.processing=!0;try{do{this.pendingRead=!1;let t=this.readAppendedChunk();if(!t||!this.looksLikePromptDelta(t))continue;let i=await this.captureSnapshot();if(!i||!this.looksLikePromptSnapshot(i))continue;let s=this.hashPromptSnapshot(i);s!==this.lastPromptHash&&(this.lastPromptHash=s,this.emit("prompt-candidate",{rawDelta:t,snapshot:i,detectedAt:Date.now()}))}while(this.pendingRead)}catch(t){n.error("Failed to process tmux pane changes",{error:t}),this.emit("observer-error",t)}finally{this.processing=!1}}}readAppendedChunk(){if(!this.pipeFilePath)return"";let t=I.statSync(this.pipeFilePath);if(t.size<=this.filePosition)return"";let i=I.openSync(this.pipeFilePath,"r");try{let s=t.size-this.filePosition,r=Buffer.alloc(s);return I.readSync(i,r,0,s,this.filePosition),this.filePosition=t.size,r.toString("utf-8")}finally{I.closeSync(i)}}looksLikePromptDelta(t){return/\[(?:y\/n|Y\/n|y\/N)\]|\b(?:apply|approve|allow|reject|deny|continue)\b/i.test(t)}looksLikePromptSnapshot(t){let i=t.split(`
9
- `).slice(-20).join(`
10
- `);return/\[(?:y\/n|Y\/n|y\/N)\]|^\s*\d+\.\s+/im.test(i)}hashPromptSnapshot(t){let i=t.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g,"").replace(/\r/g,`
1
+ "use strict";var tt=Object.create;var W=Object.defineProperty;var it=Object.getOwnPropertyDescriptor;var st=Object.getOwnPropertyNames;var rt=Object.getPrototypeOf,nt=Object.prototype.hasOwnProperty;var ot=(p,t)=>{for(var e in t)W(p,e,{get:t[e],enumerable:!0})},we=(p,t,e,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of st(t))!nt.call(p,s)&&s!==e&&W(p,s,{get:()=>t[s],enumerable:!(i=it(t,s))||i.enumerable});return p};var E=(p,t,e)=>(e=p!=null?tt(rt(p)):{},we(t||!p||!p.__esModule?W(e,"default",{value:p,enumerable:!0}):e,p)),at=p=>we(W({},"__esModule",{value:!0}),p);var Xt={};ot(Xt,{CodexCompanionServer:()=>se,extractShellCommandFromPromptText:()=>et,extractShellCommandPartsFromPromptText:()=>Se});module.exports=at(Xt);var Je=require("uuid"),ye=E(require("crypto")),D=E(require("fs")),z=E(require("path")),Qe=E(require("os")),ve=require("util"),re=require("child_process"),u=require("@quantiya/codevibe-core");var Ie=E(require("os")),Pe=E(require("path")),_e=require("@quantiya/codevibe-core"),n=(0,_e.createLogger)({name:"codevibe-codex",logFile:Pe.default.join(Ie.default.tmpdir(),"codevibe-codex-mcp.log"),level:"debug"});var Ee=require("events"),b=E(require("fs")),C=E(require("path")),Te=require("string_decoder"),xe=require("chokidar"),Re=require("@quantiya/codevibe-core");var le=256*1024,lt=64*1024*1024,ct=16*1024*1024,pt=64*1024,j=class extends Ee.EventEmitter{constructor(e={}){super();this.watcher=null;this.filePositions=new Map;this.activeLogFile=null;this.sessionId=null;this.isWatching=!1;this.startTime=0;this.sessionsDir=null;this.activeReads=new Map;this.pendingReads=new Set;this.readGeneration=0;this.oversizedRecordFiles=new Set;this.fileIdentities=new Map;this.bindSource=null;this.authoritativeSessionId=null;this.pendingTranscriptBind=null;this.transcriptBindGeneration=0;this.transcriptBindingsClosed=!1;this.activeTranscriptAdmissions=new Set;this.startBaselines=new Map;if(this.maxBacklogBytes=e.maxBacklogBytes??lt,this.maxRecordBytes=e.maxRecordBytes??ct,!Number.isSafeInteger(this.maxBacklogBytes)||this.maxBacklogBytes<=0)throw new RangeError("maxBacklogBytes must be a positive safe integer");if(!Number.isSafeInteger(this.maxRecordBytes)||this.maxRecordBytes<=0)throw new RangeError("maxRecordBytes must be a positive safe integer")}start(){if(this.isWatching){n.warn("Session log watcher already running");return}this.transcriptBindGeneration++,this.transcriptBindingsClosed=!1;let e=(0,Re.getConfig)().codex.sessionsDir;this.sessionsDir=e,n.info("Starting Codex session log watcher",{sessionsDir:e}),b.existsSync(e)||(n.info("Codex sessions directory does not exist yet, creating...",{sessionsDir:e}),b.mkdirSync(e,{recursive:!0})),this.startTime=Date.now(),this.readGeneration++,this.bindSource=null,this.authoritativeSessionId=null,this.snapshotStartBaselines(e),this.drainPendingTranscriptBind(),this.watcher=(0,xe.watch)(e,{persistent:!0,ignoreInitial:!0,awaitWriteFinish:{stabilityThreshold:100,pollInterval:50},depth:4,ignored:i=>{let s=C.basename(i);return b.existsSync(i)&&b.statSync(i).isDirectory()?!1:!s.startsWith("rollout-")||!s.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=>{n.error("Watcher error:",i),this.emit("error",i)}),this.watcher.on("ready",()=>{n.info("Session log watcher ready"),this.activeLogFile||n.debug("No authoritative transcript hook received yet; rollout watcher remains fail-closed")}),this.isWatching=!0,n.info("Session log watcher started")}stop(){this.transcriptBindingsClosed=!0,this.transcriptBindGeneration++,this.watcher&&(this.watcher.close(),this.watcher=null),this.isWatching=!1,this.readGeneration++,this.filePositions.clear(),this.activeReads.clear(),this.pendingReads.clear(),this.oversizedRecordFiles.clear(),this.activeLogFile=null,this.sessionId=null,this.sessionsDir=null,this.bindSource=null,this.authoritativeSessionId=null,this.pendingTranscriptBind&&(this.pendingTranscriptBind.resolve({status:"rejected",reason:"watcher_stopped"}),this.pendingTranscriptBind=null),this.startBaselines.clear(),this.fileIdentities.clear(),n.info("Session log watcher stopped")}async waitForTranscriptAdmissions(){for(;this.activeTranscriptAdmissions.size>0;)await Promise.all(Array.from(this.activeTranscriptAdmissions,e=>e.then(()=>{},()=>{})))}getSessionId(){return this.sessionId}getActiveLogFile(){return this.activeLogFile}getBindSource(){return this.bindSource}getAuthoritativeSessionId(){return this.authoritativeSessionId}tryRealpath(e){try{return b.realpathSync(e)}catch{return null}}isInteractiveRollout(e){return!(!e||e.originator==="codex_exec"||e.threadSource==="subagent"||e.sourceSubagent)}identityOf(e){return{dev:e.dev,ino:e.ino}}identitiesEqual(e,i){return e.dev===i.dev&&e.ino===i.ino}computeSeedOffset(e){let i;try{i=b.statSync(e)}catch{return 0}if(this.getSessionAgeMs(e,i)>=this.startTime)return 0;let r=this.startBaselines.get(e);return r&&this.identitiesEqual(r.identity,this.identityOf(i))?r.size:i.size}applyBind(e,i){this.readGeneration++,this.activeReads.delete(e),this.pendingReads.delete(e),this.activeLogFile=e,this.bindSource=i,i==="heuristic"&&(this.authoritativeSessionId=null),this.filePositions.has(e)||this.filePositions.set(e,this.computeSeedOffset(e));try{this.fileIdentities.set(e,this.identityOf(b.statSync(e)))}catch{this.fileIdentities.delete(e)}this.scheduleReadNewLines(e)}bindToFile(e){let i=this.tryRealpath(e);if(!i){n.warn("bindToFile: could not canonicalize path; skipping",{filePath:e});return}if(i===this.activeLogFile){this.bindSource="authoritative";return}n.info("bindToFile: authoritative bind to session rollout",{filePath:i}),this.applyBind(i,"authoritative")}async bindToSessionTranscript(e,i){if(typeof e!="string"||e.length===0)return{status:"rejected",reason:"missing_transcript_path"};if(this.transcriptBindingsClosed)return{status:"rejected",reason:"watcher_stopped"};if(!this.sessionsDir){let s=this.pendingTranscriptBind;if(s)return s.transcriptPath===e&&s.expectedSessionId===i?{status:"pending",completion:s.completion}:(n.warn("bindToSessionTranscript: conflicting startup bind rejected",{transcriptPath:e,expectedSessionId:i,pendingExpectedSessionId:s.expectedSessionId}),{status:"rejected",reason:"conflicting_startup_bind"});let r,o=new Promise(a=>{r=a});return this.pendingTranscriptBind={transcriptPath:e,expectedSessionId:i,completion:o,resolve:r},n.debug("bindToSessionTranscript: watcher not started yet; queued pending authoritative bind",{transcriptPath:e,expectedSessionId:i}),{status:"pending",completion:o}}return this.trackTranscriptAdmission(this.validateAndBindSessionTranscript(e,i,this.transcriptBindGeneration))}trackTranscriptAdmission(e){let i;return i=e.finally(()=>{this.activeTranscriptAdmissions.delete(i)}),this.activeTranscriptAdmissions.add(i),i}isTranscriptAdmissionCurrent(e,i){return!this.transcriptBindingsClosed&&this.transcriptBindGeneration===e&&this.sessionsDir===i}async validateAndBindSessionTranscript(e,i,s){let r=this.sessionsDir;if(!r||!this.isTranscriptAdmissionCurrent(s,r))return{status:"rejected",reason:"watcher_not_started"};let o=this.tryRealpath(e),a=this.tryRealpath(r);if(!o||!a)return{status:"rejected",reason:"canonicalization_failed"};let l=C.relative(a,o);if(l===".."||l.startsWith(".."+C.sep)||C.isAbsolute(l))return n.warn("bindToSessionTranscript: path not under sessionsDir; rejecting",{transcriptPath:o}),{status:"rejected",reason:"outside_sessions_directory"};let c=C.basename(o);if(!c.startsWith("rollout-")||!c.endsWith(".jsonl"))return{status:"rejected",reason:"invalid_rollout_filename"};let d=null;for(let h=0;h<3;h++){if(!this.isTranscriptAdmissionCurrent(s,r))return{status:"rejected",reason:"watcher_stopped"};if(d=this.readSessionMeta(o),d)break;if(await new Promise(g=>setTimeout(g,20)),!this.isTranscriptAdmissionCurrent(s,r))return{status:"rejected",reason:"watcher_stopped"}}return!d||d.id!==i?(n.debug("bindToSessionTranscript: meta missing or id mismatch; not binding",{transcriptPath:o,metaId:d?.id,expectedSessionId:i}),{status:"rejected",reason:"session_metadata_mismatch"}):this.isInteractiveRollout(d)?this.isTranscriptAdmissionCurrent(s,r)?(this.bindToFile(o),this.authoritativeSessionId=i,{status:"bound"}):{status:"rejected",reason:"watcher_stopped"}:(n.debug("bindToSessionTranscript: non-interactive rollout (codex_exec/subagent); not binding",{transcriptPath:o,originator:d.originator,threadSource:d.threadSource}),{status:"rejected",reason:"non_interactive_rollout"})}async drainPendingTranscriptBind(){let e=this.pendingTranscriptBind;if(!e)return;this.pendingTranscriptBind=null;let i;try{this.transcriptBindingsClosed?i={status:"rejected",reason:"watcher_stopped"}:i=await this.trackTranscriptAdmission(this.validateAndBindSessionTranscript(e.transcriptPath,e.expectedSessionId,this.transcriptBindGeneration))}catch(s){n.warn("bindToSessionTranscript: pending startup bind failed",{expectedSessionId:e.expectedSessionId,error:s instanceof Error?s.message:String(s)}),i={status:"rejected",reason:"startup_bind_failed"}}e.resolve(i)}onFileAdded(e){let i=this.tryRealpath(e);i&&(i===this.activeLogFile?this.scheduleReadNewLines(i):n.debug("Ignoring unowned rollout add; awaiting authoritative hook binding",{filePath:i}))}onFileChanged(e){let i=this.tryRealpath(e);i&&(this.activeLogFile&&i!==this.activeLogFile||this.activeLogFile&&this.scheduleReadNewLines(i))}readSessionMeta(e){let i=this.readFirstLogEntry(e);if(!i)return null;let s=i.payload||{};return{id:typeof s.id=="string"?s.id:void 0,timestamp:typeof i.timestamp=="string"?i.timestamp:void 0,originator:typeof s.originator=="string"?s.originator:void 0,cwd:typeof s.cwd=="string"?s.cwd:void 0,threadSource:typeof s.thread_source=="string"?s.thread_source:void 0,sourceSubagent:!!(s.source&&typeof s.source=="object"&&s.source.subagent)}}readFirstLogEntry(e){let i=null;try{i=b.openSync(e,"r");let s=new Te.StringDecoder("utf8"),r=Buffer.alloc(64*1024),o="",a=0,l=-1;for(;a<le;){let d=b.readSync(i,r,0,r.length,a);if(d<=0||(a+=d,o+=s.write(r.subarray(0,d)),l=o.indexOf(`
2
+ `),l!==-1))break}o+=s.end();let c=l===-1?o:o.slice(0,l);return c.trim()?JSON.parse(c):null}catch{return null}finally{if(i!==null)try{b.closeSync(i)}catch{}}}snapshotStartBaselines(e){this.startBaselines.clear();try{for(let{filePath:i,size:s}of this.collectRecentSessionFiles(e,!0)){let r=this.tryRealpath(i);if(r)try{let o=b.statSync(r);this.startBaselines.set(r,{size:s,identity:this.identityOf(o)})}catch{}}}catch{}}getSessionAgeMs(e,i){let s=this.readSessionMeta(e);if(s?.timestamp){let r=Date.parse(s.timestamp);if(!Number.isNaN(r))return r}return i.birthtimeMs||i.ctimeMs}collectRecentSessionFiles(e,i=!1){let s=[],r=[e];for(;r.length>0;){let o=r.pop();if(!o)continue;let a;try{a=b.readdirSync(o,{withFileTypes:!0})}catch{continue}for(let l of a){let c=C.join(o,l.name);if(l.isDirectory()){r.push(c);continue}if(!(!l.isFile()||!l.name.startsWith("rollout-")||!l.name.endsWith(".jsonl")))try{let d=b.statSync(c),h=d.birthtimeMs||d.ctimeMs,g=d.mtimeMs;(i||h>=this.startTime||g>=this.startTime-5e3)&&s.push({filePath:c,createdAt:h,modifiedAt:g,size:d.size})}catch{}}}return s}scheduleReadNewLines(e){let i=this.activeReads.get(e);if(i)return this.pendingReads.add(e),i;let s=this.readGeneration,r=Promise.resolve().then(()=>this.drainReadQueue(e,s));return this.activeReads.set(e,r),r.finally(()=>{this.activeReads.get(e)===r&&(this.activeReads.delete(e),this.pendingReads.delete(e))}),r}async drainReadQueue(e,i){do this.pendingReads.delete(e),await this.readNewLines(e,i);while(i===this.readGeneration&&this.pendingReads.has(e))}async readNewLines(e,i){if(i!==this.readGeneration)return;let s=this.filePositions.get(e)||0,r=null;try{r=b.openSync(e,b.constants.O_RDONLY|(b.constants.O_NOFOLLOW??0));let o=b.fstatSync(r),a=this.identityOf(o),l=this.fileIdentities.get(e);if(l&&!this.identitiesEqual(l,a)){let y=this.readFirstLogEntryFromFd(r),S=y?.type==="session_meta"?y.payload.id:void 0;if(this.authoritativeSessionId&&S!==this.authoritativeSessionId){let I=new Error("Authoritative Codex rollout path was replaced by a different session");n.error("Refusing replaced rollout with mismatched session identity",{filePath:e,expectedSessionId:this.authoritativeSessionId,replacementSessionId:S}),this.emit("error",I);return}n.info("Session log inode replaced; resetting byte and decoder state",{filePath:e,oldPosition:s,oldIdentity:l,newIdentity:a}),s=0,this.filePositions.set(e,0),this.oversizedRecordFiles.delete(e)}if(this.fileIdentities.set(e,a),o.size<s&&(n.info("Session log truncated/rotated; resetting cursor to 0",{filePath:e,oldPosition:s,size:o.size}),s=0,this.filePositions.set(e,0),this.oversizedRecordFiles.delete(e)),o.size<=s)return;if(o.size-s>this.maxBacklogBytes){if(s===0&&this.sessionId===null){let S=this.readFirstLogEntryFromFd(r);S?.type==="session_meta"&&this.processLogEntry(S)}let y=this.findRecentRecordBoundary(r,o.size);if(n.warn("Codex rollout backlog exceeded live-mirror limit; skipping archival prefix",{filePath:e,oldPosition:s,newPosition:y.position,size:o.size,maxBacklogBytes:this.maxBacklogBytes,discardUntilNewline:y.discardUntilNewline}),s=y.position,this.filePositions.set(e,s),y.discardUntilNewline?this.oversizedRecordFiles.add(e):this.oversizedRecordFiles.delete(e),o.size<=s)return}let c=o.size,d=Buffer.allocUnsafe(64*1024),h=s,g=[],v=0,m=this.oversizedRecordFiles.has(e);for(;h<c;){if(i!==this.readGeneration)return;let y=Math.min(d.length,c-h),S=b.readSync(r,d,0,y,h);if(S<=0)break;let I=0;for(;I<S;){if(i!==this.readGeneration)return;let w=d.subarray(0,S).indexOf(10,I),T=w!==-1,H=T?w:S,ne=H-I,be=ne+(T?1:0);if(m){h+=be,this.filePositions.set(e,h),T&&(m=!1,this.oversizedRecordFiles.delete(e)),I=T?w+1:S;continue}if(ne>0&&(g.push(Buffer.from(d.subarray(I,H))),v+=ne),h+=be,v>this.maxRecordBytes){n.warn("Discarding oversized Codex rollout JSONL record",{filePath:e,recordEnd:h,maxRecordBytes:this.maxRecordBytes}),this.filePositions.set(e,h),g=[],v=0,m=!T,m?this.oversizedRecordFiles.add(e):this.oversizedRecordFiles.delete(e),I=T?w+1:S;continue}if(!T){I=S;continue}let F=Buffer.concat(g,v);g=[],v=0,F.length>0&&F[F.length-1]===13&&(F=F.subarray(0,F.length-1));let oe=F.toString("utf8");if(oe.trim())try{let ae=JSON.parse(oe);this.processLogEntry(ae)}catch(ae){n.warn("Failed to parse log line",{filePath:e,line:oe.substring(0,100),error:ae})}if(i===this.readGeneration&&this.filePositions.set(e,h),i!==this.readGeneration)return;I=w+1}}}catch(o){n.error("Error reading log file",{filePath:e,error:o}),this.emit("error",o)}finally{if(r!==null)try{b.closeSync(r)}catch{}}}findRecentRecordBoundary(e,i){let s=Math.max(0,i-this.maxBacklogBytes),r=s+Math.min(i-s,this.maxRecordBytes);{if(s===0)return{position:0,discardUntilNewline:!1};let o=Buffer.allocUnsafe(1);if(b.readSync(e,o,0,1,s-1)===1&&o[0]===10)return{position:s,discardUntilNewline:!1};let a=Buffer.allocUnsafe(pt),l=s;for(;l<r;){let c=Math.min(a.length,r-l),d=b.readSync(e,a,0,c,l);if(d<=0)break;let h=a.subarray(0,d).indexOf(10);if(h!==-1)return{position:l+h+1,discardUntilNewline:!1};l+=d}if(l===r&&l<i&&r-s===this.maxRecordBytes){let c=Buffer.allocUnsafe(1);if(b.readSync(e,c,0,1,l)===1&&c[0]===10)return{position:l+1,discardUntilNewline:!1}}return{position:s,discardUntilNewline:!0}}}readFirstLogEntryFromFd(e){try{let i=[],s=0,r=Buffer.allocUnsafe(64*1024);for(;s<le;){let a=Math.min(r.length,le-s),l=b.readSync(e,r,0,a,s);if(l<=0)break;let c=r.subarray(0,l).indexOf(10);if(c!==-1){i.push(Buffer.from(r.subarray(0,c)));break}i.push(Buffer.from(r.subarray(0,l))),s+=l}if(i.length===0)return null;let o=Buffer.concat(i).toString("utf8");return o.trim()?JSON.parse(o):null}catch{return null}}processLogEntry(e){if(n.debug("Processing log entry",{type:e.type}),e.type==="session_meta"){let i=e.payload;this.sessionId=i.id,n.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 pe=require("uuid"),x=require("@quantiya/codevibe-core");var k=new Map,N=[],dt=24;function L(p){return typeof p=="string"?p.trim():""}function Fe(p){return typeof p=="string"?p:""}function Ce(p,t,e){let i=L(e);if(!i)return!1;let s=Fe(t);return N.some(r=>r.text===i&&r.phase===s&&r.carrier!==p)}function ut(p){let t=L(p);return t.length>0&&N.some(e=>e.text===t&&e.phase==="final_answer")}function ce(p,t,e){let i=L(e);if(i)for(N.push({carrier:p,phase:Fe(t),text:i});N.length>dt;)N.shift()}function G(){N.length=0}function ht(p){let t=p?.content;return Array.isArray(t)?t.filter(e=>e&&(e.type==="output_text"||e.type==="text")).map(e=>typeof e?.text=="string"?e.text:"").join(""):""}function mt(p){let t=p?.summary;return!Array.isArray(t)||t.length===0?"":t.map(e=>typeof e=="string"?e:typeof e?.text=="string"?e.text:"").join(`
3
+ `).trim()}function ft(p){if(typeof p!="string")return"";let t=p.match(/"cmd"\s*:\s*"((?:\\.|[^"\\])*)"/);if(t)try{return JSON.parse(`"${t[1]}"`)}catch{return t[1]}return""}var gt=new Set(["exec","shell","shell_command","bash","run_command","run_cmd"]);function Oe(p,t){return typeof t!="string"?!1:p==="apply_patch"?!0:typeof p=="string"&&gt.has(p)?!1:typeof t=="string"&&t.includes("*** Begin Patch")}function ke(p,t){let e={sessionId:t,source:x.EventSource.DESKTOP};if(p.type==="event_msg"&&p.payload){let i=p.payload.type;switch(i){case"user_message":return G(),{...e,type:x.EventType.USER_PROMPT,content:p.payload.message||"",metadata:{images:p.payload.images||[]}};case"agent_message":{let s=p.payload.message||"",r=p.payload.phase;return Ce("agent_message",r,s)?null:(ce("agent_message",r,s),{...e,type:x.EventType.ASSISTANT_RESPONSE,content:s,metadata:{phase:r}})}case"agent_reasoning":return{...e,type:x.EventType.REASONING,content:p.payload.text||""};case"task_started":return G(),n.debug("Skipping task_started entry (turn boundary)"),null;case"task_complete":{let s=L(p.payload.last_agent_message),r=null;return s&&!ut(s)?(ce("task_complete","final_answer",s),r={...e,type:x.EventType.ASSISTANT_RESPONSE,content:s,metadata:{phase:"final_answer"}}):n.debug("Skipping task_complete (final already emitted or empty)"),G(),r}case"token_count":return n.debug("Skipping token_count entry"),null;default:return n.debug("Unknown event_msg type",{type:i}),null}}if(p.type==="response_item"&&p.payload){let i=p.payload.type;if(i==="message"){if(p.payload.role!=="assistant")return n.debug("Skipping non-assistant response_item/message",{role:p.payload.role}),null;let s=ht(p.payload),r=p.payload.phase;return!L(s)||Ce("response_item",r,s)?null:(ce("response_item",r,s),{...e,type:x.EventType.ASSISTANT_RESPONSE,content:s,metadata:{phase:r}})}if(i==="reasoning"){let s=mt(p.payload);return s?{...e,type:x.EventType.REASONING,content:s}:(n.debug("Skipping reasoning response_item (no readable summary)"),null)}if(i==="function_call"){let{name:s,arguments:r,call_id:o}=p.payload,a={};try{a=JSON.parse(r||"{}")}catch{a={raw:r}}let l=(0,pe.v4)();k.set(o,{name:s,input:r,eventId:l});let c=B(s),d=yt(s,a);return{...e,type:x.EventType.TOOL_USE,content:d,metadata:{toolName:c,toolInput:a,callId:o,status:"running"}}}if(i==="function_call_output"){let{call_id:s,output:r}=p.payload,o=k.get(s);k.delete(s);let a=o?.name?B(o.name):"Tool",l=de(V(r),500);return{...e,type:x.EventType.TOOL_USE,content:`${a} completed:
4
+ ${l}`,metadata:{toolName:a,toolOutput:r,callId:s,status:"completed"}}}if(i==="custom_tool_call"){let{name:s,call_id:r,input:o,status:a}=p.payload;if(k.set(r,{name:s,input:o,eventId:(0,pe.v4)()}),Oe(s,o)){let d=St(o),{oldString:h,newString:g}=vt(o),v=d?`Editing: ${d.filePath}`:"Applying patch";return{...e,type:x.EventType.TOOL_USE,content:v,metadata:{tool_name:"Edit",tool_input:{file_path:d?.filePath||"",old_string:h,new_string:g},callId:r,status:a||"running"}}}let l=ft(o),c=B(s==="exec"?"shell":s||"tool");return{...e,type:x.EventType.TOOL_USE,content:l?`Running: ${l}`:`Running ${c}`,metadata:{toolName:c,toolInput:l?{command:l}:{raw:typeof o=="string"?o:""},callId:r,status:a||"running"}}}if(i==="custom_tool_call_output"){let{call_id:s,output:r}=p.payload,o=k.get(s);if(k.delete(s),Oe(o?.name,o?.input)){let c={};if(typeof r=="string")try{c=JSON.parse(r||"{}")}catch{c={raw:r}}else if(Array.isArray(r)){let y=V(r);try{c=JSON.parse(y)}catch{c={raw:r,text:y}}}else c=r??{};let d=c!==null&&typeof c=="object"&&!Array.isArray(c)?c:null,h=typeof d?.output=="string"?d.output:void 0,g=d?.error,v=h!==void 0&&h.includes("Success"),m=typeof g=="string"?g:g!==void 0?JSON.stringify(g):"Unknown error";return{...e,type:x.EventType.TOOL_USE,content:v?"File edit applied successfully":`Edit failed: ${m}`,metadata:{toolName:"Edit",toolOutput:c,callId:s,status:"completed",success:v}}}let a=o?.name?B(o.name==="exec"?"shell":o.name):"Tool",l=V(r);return{...e,type:x.EventType.TOOL_USE,content:`${a} completed:
5
+ ${de(l,500)}`,metadata:{toolName:a,toolOutput:r,callId:s,status:"completed"}}}return n.debug("Unknown response_item type",{type:i}),null}return p.type==="turn_context"?(n.debug("Skipping turn_context entry"),null):(n.debug("Unhandled log entry type",{type:p.type}),null)}function B(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 yt(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 ${B(p)}`}}function vt(p){let t=[],e=[],i=/^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/;for(let s of p.split(`
6
+ `))if(!(i.test(s)||s.startsWith("***")||s.startsWith("---")||s.startsWith("+++"))){if(s.startsWith("-"))t.push(s.slice(1));else if(s.startsWith("+"))e.push(s.slice(1));else if(s.startsWith(" ")){let r=s.slice(1);t.push(r),e.push(r)}}return{oldString:t.join(`
7
+ `),newString:e.join(`
8
+ `)}}function St(p){if(!p)return null;let t=p.match(/\*\*\* (?:Update|Add|Delete) File: (.+)/);return t?{filePath:t[1].trim()}:null}function V(p){if(p==null)return"";if(typeof p=="string")return p;if(Array.isArray(p))return p.map(t=>typeof t?.text=="string"?t.text:t?.type==="input_image"||typeof t?.image_url=="string"?"[image]":JSON.stringify(t??null)).join(`
9
+ `);if(typeof p=="object"&&typeof p.text=="string")return p.text;try{return JSON.stringify(p)??""}catch{return String(p)}}function de(p,t){return typeof p!="string"?de(V(p),t):p?p.length<=t?p:p.substring(0,t)+"...":""}function ue(){k.clear(),G()}var _=require("@quantiya/codevibe-core"),Ae=3e3,bt=8,wt=10,It=100;function Me(p){let t=Buffer.byteLength(p.carryover);return{status:"partial",progressKey:`${p.fileIdentity.dev}:${p.fileIdentity.ino}:${p.size}:${p.endOfComplete}:${t}`,size:p.size,carryoverBytes:t,endOfComplete:p.endOfComplete}}var Pt="tool_activity",U=class{constructor(t){this.consolidator=null;this.ledger=null;this.sessionMap=null;this.ownership=null;this.cursor=null;this.backendSessionId=null;this.rolloutId=null;this.transcriptPath=null;this.backstopTimer=null;this.backstopInFlight=null;this.cursorReady=Promise.resolve();this.cursorRepaired=!1;this.deps=t}static mintBackendSessionId(){return _.ToolActivitySessionMap.mintCodexBackendSessionId()}async recordedBackendSessionId(t){return new _.ToolActivitySessionMap({agent:"codex",logger:this.deps.logger,lock:new _.InProcessRolloutLock,root:this.deps.root}).ownerBackendSessionId(t)}get sessionId(){return this.backendSessionId}get rollout(){return this.rolloutId}isActive(){return this.consolidator!==null}async start(t,e,i){this.backendSessionId=t,this.rolloutId=e;let s=(0,_.createRolloutLock)({agent:"codex",logger:this.deps.logger,socketDir:this.deps.socketDir,root:this.deps.root});this.sessionMap=new _.ToolActivitySessionMap({agent:"codex",logger:this.deps.logger,lock:s,root:this.deps.root}),this.ownership=await this.sessionMap.claimOwnership(e,t);let r=new _.ToolActivityOutbox({agent:"codex",logger:this.deps.logger,root:this.deps.root});this.ledger=new _.ToolActivityLedger({agent:"codex",sessionId:t,logger:this.deps.logger,root:this.deps.root}),this.consolidator=new _.ToolActivityConsolidator({agent:"codex",sessionId:t,logger:this.deps.logger,outbox:r,ledger:this.ledger,transport:this.buildTransport()}),await this.consolidator.init(),i&&this.bindTranscript(i),this.deps.logger.info("[tool-activity] codex consolidator started",{sessionId:t,rollout:e,hasTranscript:!!i})}bindTranscript(t){if(!this.consolidator||!t||this.transcriptPath===t)return;this.transcriptPath=t;let e=new _.ToolReplayCursor({agent:"codex",sessionId:this.backendSessionId,canonicalPath:t,logger:this.deps.logger,root:this.deps.root,readBytes:this.deps.replayReadBytes,maxFrameBytes:this.deps.replayMaxFrameBytes});this.cursor=e,this.cursorRepaired=!1,this.cursorReady=e.load().catch(s=>this.deps.logger.warn("[tool-activity] codex cursor load failed (rescanning fresh)",{sessionId:this.backendSessionId,err:String(s)}));let i=this.deps.backstopIntervalMs??3e3;i>0&&!this.backstopTimer&&(this.backstopTimer=setInterval(()=>{this.runTranscriptBackstop().catch(s=>this.deps.logger.warn("[tool-activity] codex transcript backstop failed",{sessionId:this.backendSessionId,err:String(s)}))},i),this.backstopTimer.unref?.())}async observeCall(t,e){if(!this.consolidator||!this.sessionMap||!this.ownership||!t.callId||!t.toolName)return;let i=this.consolidator;await this.sessionMap.bindCall(this.ownership,t.callId);let s={id:t.callId,tool:t.toolName,normalizedTarget:xt(t.toolName,t.toolInput),ts:t.ts??new Date().toISOString(),byteOffset:t.byteOffset??0,...t.toolInput!==void 0?{digest:(0,_.digestOf)(t.toolInput)}:{}},r=await i.observe(s,e);return r==="closed"?await i.captureTerminal(s,this.deps.shutdownDrainMs??Ae)?"sealed-terminal":"closed":r}async runTranscriptBackstop(t=!1){await this.runTranscriptBackstopTurn(t)}runTranscriptBackstopTurn(t=!1){if(this.backstopInFlight)return this.backstopInFlight;let e=this.cursor,i=this.cursorReady,s=this.consolidator;if(!s||!e)return Promise.resolve("caught-up");let o=(async()=>{if(await i,this.cursor!==e)return"more";if(!this.cursorRepaired){if(await s.repairLostWindows(e),this.cursor!==e)return"more";this.cursorRepaired=!0}return this.doTranscriptBackstop(e,t)})().finally(()=>{this.backstopInFlight===o&&(this.backstopInFlight=null)});return this.backstopInFlight=o,o}async doTranscriptBackstop(t,e=!1){let i=this.consolidator;if(!i)return"caught-up";this.ledger&&await this.ledger.reloadLegacyOwned();for(let s=0;s<bt;s+=1){if(this.cursor!==t)return"caught-up";let r=await t.readFrames();if(r.missing)return"caught-up";if(r.frames.length===0)return r.carryover.length>0?Me(r):"caught-up";let o=Number.POSITIVE_INFINITY,a=e?{includeInFlight:!0}:void 0;for(let l of r.frames)for(let c of _t(l.line,l.startOffset))await this.observeCall(c,a)==="deferred-in-flight"&&(o=Math.min(o,l.startOffset));if(await i.flush(),o<r.endOfComplete)return await t.checkpoint(o,{fileIdentity:r.fileIdentity,size:r.size,carryover:""}),"more";if(await t.checkpoint(r.endOfComplete,{fileIdentity:r.fileIdentity,size:r.size,carryover:r.carryover}),!r.hasMore)return r.carryover.length>0?Me(r):"caught-up"}return"more"}async flush(){await this.consolidator?.flush()}async stop(){this.backstopTimer&&(clearInterval(this.backstopTimer),this.backstopTimer=null),await this.runTranscriptBackstop().catch(()=>{});let t=Math.max(1,Math.trunc(this.deps.shutdownPartialNoProgressTurns??It)),e=null,i=0;for(;;){let s=await this.runTranscriptBackstopTurn(!0);if(s==="caught-up")break;if(typeof s!="string"){if(s.progressKey===e?i+=1:(e=s.progressKey,i=1),i>=t){this.deps.logger.warn("[tool-activity] deferring unchanged incomplete rollout suffix during shutdown; the next owner will re-read it from the durable complete-line boundary",{size:s.size,carryoverBytes:s.carryoverBytes,endOfComplete:s.endOfComplete,unchangedTurns:i});break}await new Promise(r=>setTimeout(r,wt))}else e=null,i=0,await new Promise(r=>setImmediate(r))}await this.consolidator?.drainSends(this.deps.shutdownDrainMs??Ae).catch(()=>{});try{await this.consolidator?.shutdown()}finally{await this.ownership?.release().catch(()=>{}),this.ownership=null,this.consolidator=null,this.ledger=null,this.sessionMap=null,this.cursor=null}}buildTransport(){let t=async(e,i,s)=>{let r=s?s.sessionKey:this.deps.getSessionKey();if(!r)throw new Error("[tool-activity] no session key at send (retryable)");let o=s?s.sessionKeyGen:this.deps.getSessionKeyGen();await this.deps.createEvent({sessionId:e,type:_.EventType.TOOL_ACTIVITY,source:_.EventSource.DESKTOP,content:this.deps.encryptContent(Pt,r),metadata:{encrypted:this.deps.encryptMetadata({manifest:i.manifest},r)},timestamp:i.windowStart,isEncrypted:!0,clientEventId:i.clientEventId,...o?{expectedSessionKeyGen:o}:{}})};return{send:async(e,i)=>{try{await t(e,i)}catch(s){if((0,_.isSessionKeyStaleError)(s)&&this.deps.refreshSessionKey){let r=await this.deps.refreshSessionKey(e);if(!r)throw s;await t(e,i,r);return}throw s}}}}};function _t(p,t){let e;try{e=JSON.parse(p)}catch{return[]}if(e?.type!=="response_item"||!e.payload)return[];let i=e.payload,s=typeof e.timestamp=="string"?e.timestamp:new Date(0).toISOString();if(i.type==="function_call"&&typeof i.call_id=="string"&&typeof i.name=="string"){let r;try{r=JSON.parse(i.arguments||"{}")}catch{r={raw:i.arguments}}return[{callId:i.call_id,toolName:Et(i.name),toolInput:r,ts:s,byteOffset:t}]}if(i.type==="custom_tool_call"&&typeof i.call_id=="string"){let r=Tt(typeof i.input=="string"?i.input:""),o=r?{file_path:r,patch:i.input}:{patch:i.input};return[{callId:i.call_id,toolName:"Edit",toolInput:o,ts:s,byteOffset:t}]}return[]}function Et(p){return p==="shell"||p==="shell_command"||p==="local_shell"?"Bash":p==="apply_patch"?"Edit":p}function Tt(p){let t=p.match(/^\*\*\* (?:Update|Add|Delete) File: (.+)$/m);return t?t[1].trim():void 0}function xt(p,t){if(!t||typeof t!="object")return;let e=t,i=r=>typeof r=="string"&&r.length>0?r:void 0,s=Array.isArray(e.command)?e.command.filter(r=>typeof r=="string").join(" ").trim().split(/\s+/)[0]:typeof e.command=="string"?e.command.trim().split(/\s+/)[0]:void 0;return i(e.file_path)??i(e.path)??i(e.pattern)??i(e.url)??(s&&s.length>0?s:void 0)}var Ne=require("events"),De=require("@quantiya/codevibe-core");var Y=class p extends Ne.EventEmitter{constructor(){super();this.pendingCalls=new Map;this.timers=new Map;this.timeoutMs=(0,De.getConfig)().codex.approvalTimeoutMs,n.info("Approval detector initialized",{timeoutMs:this.timeoutMs})}onToolCallStart(e,i,s){n.debug("Tool call started",{callId:e,name:i});let r=this.parseInput(s),o=this.extractFilePath(i,s,r),a=this.extractDiff(i,s,r),l={callId:e,name:i,input:s,filePath:o,diff:a,parsedInput:r,timestamp:Date.now(),notificationSent:!1};if(this.pendingCalls.set(e,l),!this.shouldScheduleApprovalTimeout(i,r)){n.debug("Skipping approval timeout for non-escalated tool call",{callId:e,name:i});return}let c=setTimeout(()=>{this.checkPendingCall(e)},this.timeoutMs);this.timers.set(e,c)}onToolCallComplete(e){n.debug("Tool call completed",{callId:e}),this.pendingCalls.delete(e);let i=this.timers.get(e);i&&(clearTimeout(i),this.timers.delete(e))}checkPendingCall(e){let i=this.pendingCalls.get(e);if(!i||i.notificationSent)return;let s=Date.now()-i.timestamp;n.info("Tool call still pending after timeout",{callId:e,name:i.name,elapsedMs:s}),i.notificationSent=!0,this.pendingCalls.set(e,i),this.emit("approval-pending",{callId:e,toolName:i.name,hint:this.extractHint(i.name,i.input,i.filePath),filePath:i.filePath,diff:i.diff,toolInput:i.parsedInput,rawInput:i.input,elapsedMs:s})}extractHint(e,i,s){if(s)return`File: ${s}`;if(e==="apply_patch"&&i){let r=i.match(/\*\*\* (?:Update|Add|Delete) File: (.+)/);if(r)return`File: ${r[1].trim()}`}if(p.SHELL_TOOL_NAMES.has(e))try{let r=JSON.parse(i),o=typeof r.command=="string"?r.command:typeof r.cmd=="string"?r.cmd:void 0;if(o)return`Command: ${o.substring(0,50)}${o.length>50?"...":""}`}catch{}return`Tool: ${this.mapToolName(e)}`}mapToolName(e){return{exec_command:"Bash",shell_command:"Bash",shell:"Bash",exec:"Bash",local_shell:"Bash",apply_patch:"File Edit",write_file:"Write File",read_file:"Read File"}[e]||e}parseInput(e){if(e)try{return JSON.parse(e)}catch{return}}static{this.SHELL_TOOL_NAMES=new Set(["exec_command","shell_command","shell","exec","local_shell"])}shouldScheduleApprovalTimeout(e,i){return p.SHELL_TOOL_NAMES.has(e)?i?.sandbox_permissions==="require_escalated":!1}extractFilePath(e,i,s){if(e==="apply_patch"&&i){let o=i.match(/\*\*\* (?:Update|Add|Delete) File: (.+)/);if(o)return o[1].trim()}let r=s?.file_path||s?.path||s?.filePath;if(r&&typeof r=="string")return r}extractDiff(e,i,s){if(e==="apply_patch"&&i)return i;if(s?.diff&&typeof s.diff=="string")return s.diff}getPendingCalls(){return Array.from(this.pendingCalls.values())}hasPendingCalls(){return this.pendingCalls.size>0}clear(){for(let e of this.timers.values())clearTimeout(e);this.timers.clear(),this.pendingCalls.clear(),n.debug("Approval detector cleared")}shutdown(){this.clear(),this.removeAllListeners(),n.info("Approval detector shutdown")}};var Be=require("child_process"),Le=require("util");var he=(0,Le.promisify)(Be.exec),K="__CODEVIBE_CODEX_KEY_ESCAPE__",X=class{async sendInput(t,e){n.info("Attempting to send input to Codex",{sessionId:t,input:e});try{let i=process.env.CODEVIBE_CODEX_TMUX_SESSION;return i?(n.info("Using tmux send-keys",{tmuxSession:i}),e===K?await this.sendKeyViaTmux(i,"Escape"):await this.sendViaTmux(i,e),n.info("Successfully sent input to Codex",{sessionId:t,input:e}),!0):(n.error("No tmux session found - CodeVibe Companion launch is required",{sessionId:t,hint:"Start Codex CLI using `codevibe --agent codex`"}),!1)}catch(i){return n.error("Failed to send input to Codex",{sessionId:t,error:i instanceof Error?i.message:String(i)}),!1}}async sendViaTmux(t,e){let i=e.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\$/g,"\\$").replace(/`/g,"\\`");n.info("Sending via tmux",{sessionName:t,inputLength:e.length});try{let s=`tmux send-keys -t "${t}" -l "${i}"`;await he(s),await this.delay(500);let r=`tmux send-keys -t "${t}" Enter`;await he(r),n.info("tmux send-keys completed")}catch(s){throw n.error("tmux send-keys failed",{sessionName:t,error:s}),s}}async sendKeyViaTmux(t,e){n.info("Sending special key via tmux",{sessionName:t,key:e});try{let i=`tmux send-keys -t "${t}" ${e}`;await he(i),n.info("tmux special-key send completed")}catch(i){throw n.error("tmux special-key send failed",{sessionName:t,key:e,error:i}),i}}delay(t){return new Promise(e=>setTimeout(e,t))}isApprovalResponse(t){let e=t.trim().toLowerCase();return["y","n","a","q","e","yes","no"].includes(e)||/^[0-9]+$/.test(e)}};var f=E(require("fs")),Q=E(require("os")),R=E(require("path")),q=require("crypto"),Ke=require("child_process"),qe=require("events"),$e=require("util");var me=(0,$e.promisify)(Ke.execFile),Ue=512*1024,A="writer.state",Rt=2e3,Ct=10,Ot=16*1024*1024,Ft=10080*60*1e3,J=class extends qe.EventEmitter{constructor(e={}){super();this.sessionName=null;this.started=!1;this.pipeDirPath=null;this.pipeDirIdentity=null;this.pipeFilePath=null;this.pipeFileFd=null;this.pipeFileIdentity=null;this.writerStatePath=null;this.writerStateFd=null;this.writerStateIdentity=null;this.writerGeneration=null;this.pipeEnabled=!1;this.filePosition=0;this.pollTimer=null;this.processing=!1;this.pendingRead=!1;this.promptInspectionChain=Promise.resolve();this.lastPromptHash=null;this.lifecycleGeneration=0;this.retainedMirrors=[];this.retainedMirrorContainmentLost=!1;this.maxPaneFileBytes=e.maxPaneFileBytes??Ot,this.stalePaneRetentionMs=e.stalePaneRetentionMs??Ft,this.tempDir=e.tempDir??Q.tmpdir(),this.writerPath=R.resolve(__dirname,"..","libexec","pane-mirror-writer.js");let i=e.runtimePlatform??process.platform,s=e.runtimeRelease??Q.release();if(this.wslDetected=i==="linux"&&(/microsoft/i.test(s)||!!process.env.WSL_DISTRO_NAME||!!process.env.WSL_INTEROP),!Number.isSafeInteger(this.maxPaneFileBytes)||this.maxPaneFileBytes<=0)throw new RangeError("maxPaneFileBytes must be a positive safe integer");if(!Number.isSafeInteger(this.stalePaneRetentionMs)||this.stalePaneRetentionMs<0)throw new RangeError("stalePaneRetentionMs must be a non-negative safe integer")}async start(e){if(this.wslDetected)throw new Error("Tmux pane mirror fallback is disabled on unqualified WSL; using hook-based prompts only");if(this.started&&this.sessionName===e&&this.pipeEnabled){n.debug("Tmux pane observer already started",{sessionName:e});return}this.started&&await this.stop(),this.lifecycleGeneration++,this.sessionName=e,this.filePosition=0,this.lastPromptHash=null;try{if(this.cleanupStalePipeFiles(),this.reapOwnedRetainedMirrors(),this.retainedMirrorContainmentLost||this.retainedMirrors.length>0)throw new Error("Prior bounded tmux pane writer is still unconfirmed; refusing another mirror");this.createSecureMirror(),this.started=!0,await this.enablePipePane(),this.pipeEnabled=!0,this.startPolling()}catch(i){this.started=!1,this.lifecycleGeneration++;let s=this.writerGeneration===null;if(!s)try{await this.disablePipePane(),s=!0}catch(r){n.error("Failed to confirm tmux pane writer quiescence after startup failure",{disableError:r})}throw s&&(this.pipeEnabled=!1),this.clearPolling(),s?this.releaseSecureMirror():this.abandonSecureMirror(),this.resetLifecycleState(),i}n.info("Tmux pane observer started",{sessionName:e})}async stop(){if(!this.started&&this.pipeFileFd===null)return;this.started=!1,this.lifecycleGeneration++;let e=!1;try{await this.disablePipePane(),e=!0}catch(i){n.error("Failed to confirm tmux pane writer quiescence during stop",{error:i}),this.emit("observer-error",i)}e&&(this.pipeEnabled=!1),this.clearPolling(),e?this.releaseSecureMirror():this.abandonSecureMirror(),n.info("Tmux pane observer stopped",{sessionName:this.sessionName,writerQuiesced:e}),this.resetLifecycleState()}identityOf(e){return{dev:e.dev,ino:e.ino}}identitiesEqual(e,i){return e.dev===i.dev&&e.ino===i.ino}openMirrorFile(e){return f.openSync(e,f.constants.O_RDWR|f.constants.O_CREAT|f.constants.O_EXCL|(f.constants.O_NOFOLLOW??0),384)}truncateMirror(e){f.ftruncateSync(e,0)}createSecureMirror(){f.mkdirSync(this.tempDir,{recursive:!0,mode:448});let e=f.mkdtempSync(R.join(this.tempDir,`codevibe-codex-pane-${process.pid}-`));f.chmodSync(e,448);let i=R.join(e,"pane.log"),s=R.join(e,A),r=null,o=null;try{r=this.openMirrorFile(i),f.fchmodSync(r,384);let a=f.fstatSync(r);if(!a.isFile())throw new Error("Tmux pane mirror is not a regular file");o=this.openMirrorFile(s),f.fchmodSync(o,384);let l=f.fstatSync(o);if(!l.isFile())throw new Error("Tmux pane writer state is not a regular file");f.writeSync(o,Buffer.from(`idle
10
+ `),0,5,0),this.pipeDirPath=e,this.pipeDirIdentity=this.identityOf(f.lstatSync(e)),this.pipeFilePath=i,this.pipeFileFd=r,this.pipeFileIdentity=this.identityOf(a),this.writerStatePath=s,this.writerStateFd=o,this.writerStateIdentity=this.identityOf(l),r=null,o=null}catch(a){if(r!==null)try{f.closeSync(r)}catch{}if(o!==null)try{f.closeSync(o)}catch{}try{f.unlinkSync(i)}catch{}try{f.unlinkSync(s)}catch{}try{f.rmdirSync(e)}catch{}throw a}}pathMatchesMirrorIdentity(){if(!this.pipeFilePath||!this.pipeFileIdentity)return!1;try{let e=f.lstatSync(this.pipeFilePath);return e.isFile()&&!e.isSymbolicLink()&&this.identitiesEqual(this.pipeFileIdentity,this.identityOf(e))}catch{return!1}}quarantineIfIdentityMatches(e,i){let s=`${e}.reap-${process.pid}-${(0,q.randomBytes)(6).toString("hex")}`;try{f.renameSync(e,s);let r=f.lstatSync(s);return this.identitiesEqual(i,this.identityOf(r))?s:(f.existsSync(e)||f.renameSync(s,e),null)}catch{return null}}restoreQuarantine(e,i){try{f.existsSync(i)||f.renameSync(e,i)}catch{}}pathMatchesIdentity(e,i){if(!e||!i)return!1;try{let s=f.lstatSync(e);return s.isFile()&&!s.isSymbolicLink()&&this.identitiesEqual(i,this.identityOf(s))}catch{return!1}}closeOwnedDescriptors(){if(this.pipeFileFd!==null)try{f.closeSync(this.pipeFileFd)}catch{}if(this.writerStateFd!==null)try{f.closeSync(this.writerStateFd)}catch{}this.pipeFileFd=null,this.writerStateFd=null}unlinkOwnedFile(e,i){if(!e||!i||!this.pathMatchesIdentity(e,i))return;let s=this.quarantineIfIdentityMatches(e,i);s&&f.unlinkSync(s)}releaseSecureMirror(){let e=this.pipeFilePath,i=this.writerStatePath,s=this.pipeDirPath,r=this.pipeFileIdentity,o=this.writerStateIdentity;this.closeOwnedDescriptors();try{this.unlinkOwnedFile(e,r)}catch{}try{this.unlinkOwnedFile(i,o)}catch{}if(s)try{f.rmdirSync(s)}catch{}this.pipeDirPath=null,this.pipeDirIdentity=null,this.pipeFilePath=null,this.pipeFileIdentity=null,this.writerStatePath=null,this.writerStateIdentity=null,this.writerGeneration=null}abandonSecureMirror(){let e=this.pipeDirPath,i=this.pipeFilePath;e&&this.pipeDirIdentity&&i&&this.pipeFileIdentity&&this.writerStatePath&&this.writerStateIdentity&&this.writerGeneration?this.retainedMirrors.push({dirPath:e,dirIdentity:this.pipeDirIdentity,panePath:i,paneIdentity:this.pipeFileIdentity,statePath:this.writerStatePath,stateIdentity:this.writerStateIdentity,writerGeneration:this.writerGeneration}):(e||i)&&(this.retainedMirrorContainmentLost=!0),this.closeOwnedDescriptors(),n.warn("Retaining bounded tmux pane mirror because writer quiescence is unconfirmed",{retainedDir:e,retainedFile:i,maxBytes:this.maxPaneFileBytes}),this.pipeDirPath=null,this.pipeDirIdentity=null,this.pipeFilePath=null,this.pipeFileIdentity=null,this.writerStatePath=null,this.writerStateIdentity=null,this.writerGeneration=null}resetLifecycleState(){this.sessionName=null,this.pipeDirPath=null,this.pipeDirIdentity=null,this.pipeFilePath=null,this.pipeFileFd=null,this.pipeFileIdentity=null,this.writerStatePath=null,this.writerStateFd=null,this.writerStateIdentity=null,this.writerGeneration=null,this.pipeEnabled=!1,this.filePosition=0,this.processing=!1,this.pendingRead=!1,this.lastPromptHash=null}async failClosedPipe(e){let i=!1;try{await this.disablePipePane(),i=!0}catch(s){n.error("Failed to confirm unsafe tmux pane writer quiescence",{disableError:s})}i&&(this.pipeEnabled=!1),this.clearPolling(),n.error("Tmux pane mirror entered fail-closed containment after integrity/cap failure",{error:e,writerQuiesced:i,independentlyBounded:!0}),this.emit("observer-error",e)}resetLastPromptHash(){this.lastPromptHash=null}async captureSnapshot(e=120){if(!this.sessionName)throw new Error("Tmux pane observer is not started");let i=Math.max(1,Math.floor(e));try{let{stdout:s}=await me("tmux",["capture-pane","-p","-e","-J","-S",`-${i}`,"-t",this.sessionName],{timeout:5e3,maxBuffer:2097152});return s}catch(s){throw n.error("Failed to capture tmux pane snapshot",{sessionName:this.sessionName,error:s}),this.emit("observer-error",s),s}}quoteShellArg(e){return`'${e.replace(/'/g,"'\\''")}'`}writerStatePathMatchesIdentity(){return this.pathMatchesIdentity(this.writerStatePath,this.writerStateIdentity)}writeWriterState(e){if(this.writerStateFd===null||!this.writerStateIdentity||!this.writerStatePathMatchesIdentity())throw new Error("Tmux pane writer state identity is not valid");let i=f.fstatSync(this.writerStateFd);if(!this.identitiesEqual(this.writerStateIdentity,this.identityOf(i)))throw new Error("Tmux pane writer state descriptor identity changed");let s=Buffer.from(e,"utf8");f.ftruncateSync(this.writerStateFd,0);let r=0;for(;r<s.length;){let o=f.writeSync(this.writerStateFd,s,r,s.length-r,r);if(o<=0)throw new Error("Short tmux pane writer-state write");r+=o}f.fsyncSync(this.writerStateFd)}readWriterState(){if(this.writerStateFd===null||!this.writerStateIdentity||!this.writerStatePathMatchesIdentity())throw new Error("Tmux pane writer state identity is not valid");let e=f.fstatSync(this.writerStateFd);if(!this.identitiesEqual(this.writerStateIdentity,this.identityOf(e)))throw new Error("Tmux pane writer state descriptor identity changed");let i=Math.min(e.size,512);if(i===0)return"";let s=Buffer.alloc(i),r=f.readSync(this.writerStateFd,s,0,i,0);return s.subarray(0,r).toString("utf8").trim()}async waitForWriterState(e,i,s=Rt){let r=Date.now()+s;do{let o=this.readWriterState();if(i.some(a=>o.startsWith(`${a} ${e} `)))return o;if(Date.now()>=r)return null;await new Promise(a=>setTimeout(a,Ct))}while(!0)}async enablePipePane(){if(!this.sessionName||!this.pipeFilePath||!this.writerStatePath)throw new Error("Tmux pane observer is not initialized");if(this.pipeFileFd===null||!this.pipeFileIdentity||!this.pathMatchesMirrorIdentity()||this.writerStateFd===null||!this.writerStateIdentity||!this.writerStatePathMatchesIdentity())throw new Error("Tmux pane mirror identity is not valid");let e=(0,q.randomBytes)(16).toString("hex");this.writerGeneration=e,this.writeWriterState(`starting ${e}
11
+ `);let i=[process.execPath,this.writerPath,this.pipeFilePath,String(this.pipeFileIdentity.dev),String(this.pipeFileIdentity.ino),this.writerStatePath,String(this.writerStateIdentity.dev),String(this.writerStateIdentity.ino),e,String(this.maxPaneFileBytes)].map(r=>this.quoteShellArg(r)).join(" ");try{await me("tmux",["pipe-pane","-O","-t",this.sessionName,i])}catch(r){throw this.writerGeneration=null,r}let s=await this.waitForWriterState(e,["ready","done"]);if(!s)throw new Error("Tmux pane writer did not confirm startup");if(s.startsWith(`done ${e} `))throw new Error("Tmux pane writer terminated during startup");n.debug("Enabled tmux pipe-pane mirroring",{sessionName:this.sessionName,pipeFilePath:this.pipeFilePath,writerGeneration:e})}async disablePipePane(){let e=this.writerGeneration;if(!this.sessionName||!e)return;let i=null;try{await me("tmux",["pipe-pane","-t",this.sessionName])}catch(o){i=o}if(!e){if(i)throw i;return}if(await this.waitForWriterState(e,["done"])){this.writerGeneration=null;return}let r=i instanceof Error?`: ${i.message}`:"";throw new Error(`Tmux pane writer quiescence was not confirmed${r}`)}startPolling(){this.clearPolling(),this.pollTimer=setInterval(()=>{this.processFileChanges()},100),this.pollTimer.unref?.()}clearPolling(){this.pollTimer&&clearInterval(this.pollTimer),this.pollTimer=null}async processFileChanges(){if(!(!this.started||!this.pipeFilePath)){if(this.processing){this.pendingRead=!0;return}this.processing=!0;try{do{this.pendingRead=!1;let e="";try{e=this.readAppendedChunk()}catch(i){n.error("Failed to read bounded tmux pane mirror",{error:i}),await this.failClosedPipe(i);return}await this.rotatePipeFileIfNeeded(),e&&this.queuePromptInspection(e,this.lifecycleGeneration)}while(this.pendingRead)}catch(e){n.error("Failed to process tmux pane changes",{error:e}),this.emit("observer-error",e)}finally{this.processing=!1}}}queuePromptInspection(e,i){this.promptInspectionChain=this.promptInspectionChain.then(async()=>{if(!(!this.started||this.lifecycleGeneration!==i))try{await this.inspectChunkForPrompt(e)}catch(s){n.error("Failed to inspect tmux pane delta",{error:s}),this.emit("observer-error",s)}})}async inspectChunkForPrompt(e){if(!this.looksLikePromptDelta(e))return;let i=await this.captureSnapshot();this.inspectSnapshotForPrompt(i,e)}inspectSnapshotForPrompt(e,i){let s=e?this.extractActiveApprovalBlock(e):null;if(!s){e&&n.debug("tmux delta cue matched but no active dialog isolated (no candidate emitted)",{snapshotTail:e.split(`
12
+ `).slice(-6).join("\\n")});return}let r=this.hashPromptSnapshot(s);r!==this.lastPromptHash&&(this.lastPromptHash=r,this.emit("prompt-candidate",{rawDelta:i,snapshot:s,detectedAt:Date.now()}))}queueCurrentSnapshotInspection(e){this.promptInspectionChain=this.promptInspectionChain.then(async()=>{if(!(!this.started||this.lifecycleGeneration!==e))try{let i=await this.captureSnapshot();this.inspectSnapshotForPrompt(i,"")}catch(i){n.error("Failed to inspect tmux pane snapshot after rotation",{error:i}),this.emit("observer-error",i)}})}async rotatePipeFileIfNeeded(){let e=this.pipeFilePath,i=this.pipeFileFd,s=this.pipeFileIdentity,r=this.sessionName,o=this.lifecycleGeneration;if(!e||i===null||!s||!r||!this.started)return;let a;try{let c=f.fstatSync(i);if(!this.identitiesEqual(s,this.identityOf(c)))throw new Error("Tmux pane mirror descriptor identity changed");a=c.size}catch(c){await this.failClosedPipe(c);return}if(a<this.maxPaneFileBytes)return;let l="";try{await this.disablePipePane(),this.pipeEnabled=!1}catch(c){n.error("Tmux pane mirror rotation deferred because writer quiescence is unconfirmed",{error:c}),this.emit("observer-error",c),this.clearPolling();return}if(!(!this.started||this.lifecycleGeneration!==o||this.pipeFilePath!==e)){try{l=this.readAppendedChunk()}catch(c){n.error("Failed to drain final tmux pane suffix during rotation; mirror remains intact and disabled",{error:c}),this.emit("observer-error",c),this.clearPolling();return}try{this.truncateMirror(i);let c=f.fstatSync(i);if(!this.identitiesEqual(s,this.identityOf(c))||c.size!==0)throw new Error("Tmux pane mirror truncation could not be verified");if(!this.pathMatchesMirrorIdentity())throw new Error("Tmux pane mirror path was replaced during rotation")}catch(c){n.error("Tmux pane mirror remains disabled after truncation verification failure",{error:c}),this.emit("observer-error",c),this.clearPolling();return}if(this.filePosition=0,n.info("Rotated bounded tmux pane mirror",{sessionName:r,priorBytes:a,maxBytes:this.maxPaneFileBytes}),this.started&&this.lifecycleGeneration===o&&this.pipeFilePath===e&&this.sessionName===r)try{await this.enablePipePane(),this.pipeEnabled=!0}catch(c){this.pipeEnabled=!1,n.error("Tmux pane mirror remains disabled after re-enable failure",{error:c}),this.emit("observer-error",c),this.clearPolling()}l&&this.queuePromptInspection(l,o),this.pipeEnabled&&this.queueCurrentSnapshotInspection(o)}}retainedPathMatchesIdentity(e,i,s){try{let r=f.lstatSync(e);return(s==="file"?r.isFile():r.isDirectory())&&!r.isSymbolicLink()&&(typeof process.getuid!="function"||r.uid===process.getuid())&&this.identitiesEqual(i,this.identityOf(r))}catch{return!1}}readRetainedWriterState(e,i){let s=null;try{s=f.openSync(e,f.constants.O_RDONLY|(f.constants.O_NOFOLLOW??0));let r=f.fstatSync(s);if(!r.isFile()||typeof process.getuid=="function"&&r.uid!==process.getuid()||!this.identitiesEqual(i,this.identityOf(r))||r.size>512)return null;let o=Buffer.alloc(r.size),a=r.size===0?0:f.readSync(s,o,0,r.size,0);return o.subarray(0,a).toString("utf8").trim()}catch{return null}finally{if(s!==null)try{f.closeSync(s)}catch{}}}reapOwnedRetainedMirror(e){if(!this.retainedPathMatchesIdentity(e.dirPath,e.dirIdentity,"directory"))return!1;let i;try{i=f.readdirSync(e.dirPath).sort()}catch{return!1}if(i.length!==2||i[0]!=="pane.log"||i[1]!==A||!this.retainedPathMatchesIdentity(e.panePath,e.paneIdentity,"file")||!this.retainedPathMatchesIdentity(e.statePath,e.stateIdentity,"file"))return!1;let s=new RegExp(`^done ${e.writerGeneration} \\d+ \\d+$`);if(!s.test(this.readRetainedWriterState(e.statePath,e.stateIdentity)??""))return!1;let r=this.quarantineIfIdentityMatches(e.dirPath,e.dirIdentity);if(!r)return!1;let o=!1;try{let a=R.join(r,"pane.log"),l=R.join(r,A);if(!this.retainedPathMatchesIdentity(a,e.paneIdentity,"file"))throw new Error("pane identity changed");if(!this.retainedPathMatchesIdentity(l,e.stateIdentity,"file"))throw new Error("state identity changed");if(!s.test(this.readRetainedWriterState(l,e.stateIdentity)??""))throw new Error("writer generation is no longer done");if(this.unlinkOwnedFile(a,e.paneIdentity),this.unlinkOwnedFile(l,e.stateIdentity),f.existsSync(a)||f.existsSync(l))throw new Error("retained mirror unlink was incomplete");f.rmdirSync(r),o=!0,n.info("Removed completed same-owner tmux pane mirror",{retainedDir:e.dirPath})}catch{f.existsSync(r)&&this.restoreQuarantine(r,e.dirPath)}return o}reapOwnedRetainedMirrors(){this.retainedMirrors=this.retainedMirrors.filter(e=>!this.reapOwnedRetainedMirror(e))}cleanupStalePipeFiles(){let e=Date.now()-this.stalePaneRetentionMs,i;try{i=f.readdirSync(this.tempDir,{withFileTypes:!0})}catch{return}for(let s of i){let r=/^codevibe-codex-pane-(\d+)\.log$/.exec(s.name);if(r){let c=R.join(this.tempDir,s.name),d=null;try{let h=f.lstatSync(c);if(!h.isFile()||h.isSymbolicLink()||h.mtimeMs>e||typeof process.getuid=="function"&&h.uid!==process.getuid()||this.isProcessAlive(Number(r[1]))||(d=this.quarantineIfIdentityMatches(c,this.identityOf(h)),!d))continue;f.unlinkSync(d),d=null,n.info("Removed stale legacy tmux pane mirror",{candidate:c,ageMs:Date.now()-h.mtimeMs})}catch{d&&this.restoreQuarantine(d,c)}continue}let o=/^codevibe-codex-pane-(\d+)-[A-Za-z0-9_-]+$/.exec(s.name);if(!o||!s.isDirectory())continue;let a=R.join(this.tempDir,s.name),l=null;try{let c=f.lstatSync(a);if(!c.isDirectory()||c.isSymbolicLink()||c.mtimeMs>e||typeof process.getuid=="function"&&c.uid!==process.getuid())continue;let d=Number(o[1]);if(this.isProcessAlive(d)||(l=this.quarantineIfIdentityMatches(a,this.identityOf(c)),!l))continue;let h=R.join(l,"pane.log"),g=R.join(l,A),v=f.readdirSync(l);if(v.some(m=>m!=="pane.log"&&m!==A)){this.restoreQuarantine(l,a);continue}for(let m of v.map(y=>R.join(l,y))){let y=f.lstatSync(m);if(!y.isFile()||y.isSymbolicLink()||typeof process.getuid=="function"&&y.uid!==process.getuid()){this.restoreQuarantine(l,a),l=null;break}}if(!l)continue;if(v.includes(A)){let m=f.readFileSync(g,"utf8").trim(),y=/^ready [a-f0-9]{32} (\d+)$/.exec(m);if(!(m==="idle"||/^done [a-f0-9]{32} \d+ \d+$/.test(m)||!!y)||y&&this.isProcessAlive(Number(y[1]))){this.restoreQuarantine(l,a);continue}}v.includes("pane.log")&&f.unlinkSync(h),v.includes(A)&&f.unlinkSync(g),f.rmdirSync(l),l=null,n.info("Removed stale tmux pane mirror directory",{candidate:a,ageMs:Date.now()-c.mtimeMs})}catch{l&&this.restoreQuarantine(l,a)}}}isProcessAlive(e){try{return process.kill(e,0),!0}catch(i){return i?.code==="EPERM"}}readAppendedChunk(){if(!this.pipeFilePath||this.pipeFileFd===null||!this.pipeFileIdentity)return"";if(!this.pathMatchesMirrorIdentity())throw new Error("Tmux pane mirror path was replaced");let e=f.fstatSync(this.pipeFileFd);if(!this.identitiesEqual(this.pipeFileIdentity,this.identityOf(e)))throw new Error("Tmux pane mirror descriptor identity changed");if(e.size<=this.filePosition)return"";{let s=e.size-this.filePosition>Ue?e.size-Ue:this.filePosition,r=e.size-s,o=Buffer.alloc(r),a=0;for(;a<r;){let l=f.readSync(this.pipeFileFd,o,a,r-a,s+a);if(l===0)break;a+=l}return this.filePosition=s+a,o.subarray(0,a).toString("utf-8")}}looksLikePromptDelta(e){return/Would you like to run/i.test(e)||/Press enter to confirm/i.test(e)||/\besc to cancel\b/i.test(e)||/\bYes,\s+proceed\b/i.test(e)||/\bNo,\s+and tell Codex\b/i.test(e)||/don't ask again/i.test(e)||/Requesting permission for:|Do you want to proceed\?/i.test(e)||/\[(?:y\/n|Y\/n|y\/N)\]|\b(?:apply|approve|allow|reject|deny|continue)\b/i.test(e)}looksLikePromptSnapshot(e){return this.extractActiveApprovalBlock(e)!==null}extractActiveApprovalBlock(e){let s=e.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g,"").split(`
13
+ `).map(m=>m.replace(/\s+$/,"")),r=m=>m.trim().length>0,o=m=>{let y=m.trim();return/^(?:[>›❯]\s*)?press enter to confirm(?: or esc to cancel)?$/i.test(y)||/^(?:[>›❯]\s*)?(?:press )?esc to cancel$/i.test(y)},a=m=>/Would you like to |Do you want to |The model would like to |Allow .+ to run|Requesting permission for:/i.test(m),l=m=>/^[>›❯\s]*[1-9][.)]\s+\S/.test(m),c=m=>/\[(?:y\/n|Y\/n|y\/N)\]/i.test(m),d=m=>{let y=new Set;for(let S of m)l(S)&&(/\bYes,\s+proceed\b/i.test(S)&&y.add("proceed"),/\bNo,\s+and tell Codex\b/i.test(S)&&y.add("reject"),/don't ask again/i.test(S)&&y.add("dont-ask"));return y.size},h=m=>l(m)&&(/\bYes,\s+proceed\b/i.test(m)||/\bNo,\s+and tell Codex\b/i.test(m)||/don't ask again/i.test(m)),g=-1;for(let m=s.length-1;m>=0;m--)if(r(s[m])){g=m;break}if(g===-1)return null;if(c(s[g])){let m=g;for(let y=g-1;y>=0&&g-y<=8&&!(!r(s[y])||o(s[y])||l(s[y]));y--)m=y;return s.slice(m,g+1).join(`
14
+ `)}let v=-1;for(let m=s.length-1;m>=0;m--)if(o(s[m])){v=m;break}if(v===g){let m=-1;for(let S=v-1;S>=0&&!o(s[S]);S--)if(a(s[S])){m=S;break}if(m===-1)return null;let y=s.slice(m,v+1);return y.some(l)?y.join(`
15
+ `):null}if(h(s[g])){let m=-1;for(let w=g-1;w>=0;w--){if(o(s[w]))return null;if(a(s[w])){m=w;break}}if(m===-1)return null;let y=w=>{let T=w.match(/^[>›❯\s]*([1-9])[.)]\s/);return T?parseInt(T[1],10):null},S=[],I=Number.POSITIVE_INFINITY;for(let w=g;w>m&&l(s[w]);w--){let T=y(s[w]);if(T===null||T>=I)break;S.push(s[w]),I=T}return d(S)<2?null:s.slice(m,g+1).join(`
16
+ `)}return null}hashPromptSnapshot(e){let i=e.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g,"").replace(/\r/g,`
11
17
  `).replace(/[ \t]+\n/g,`
12
- `).trim();return(0,Pe.createHash)("sha256").update(i).digest("hex")}};var C=require("@quantiya/codevibe-core");var Q=P(require("express")),x=P(require("fs")),Ee=P(require("path")),Te=P(require("os"));var rt=1800*1e3,nt=60*1e3,ot=1440*60*1e3;var z=class{constructor(){this.assignedPort=0;this.portRefreshTimer=null;this.app=(0,Q.default)(),this.setupMiddleware(),this.setupRoutes(),this.tmuxSession=process.env.CODEVIBE_CODEX_TMUX_SESSION}getPort(){return this.assignedPort}setupMiddleware(){this.app.use(Q.default.json({limit:"1mb"})),this.app.use((e,t,i)=>{n.debug(`${e.method} ${e.path}`,{body:e.body}),i()})}setupRoutes(){this.app.get("/health",(e,t)=>{t.json({success:!0,service:"codevibe-codex",pid:process.pid,data:{status:"healthy",uptime:process.uptime()}})}),this.app.post("/event",this.handleEvent.bind(this))}async handleEvent(e,t){try{let i=e.body;if(!i.session_id||!i.hook_event_name){t.status(400).json({success:!1,error:"Missing session_id or hook_event_name"});return}let s=this.transformHookToEvent(i);n.info("Received hook event",{sessionId:i.session_id,hookEvent:i.hook_event_name,type:s.type}),this.eventHandler&&await this.eventHandler(s),t.json({success:!0})}catch(i){n.error("Error handling event:",i),t.status(500).json({success:!1,error:i instanceof Error?i.message:"Unknown error"})}}transformHookToEvent(e){let t={cwd:e.cwd,hook_event_name:e.hook_event_name,...e.metadata||{}},i,s;switch(e.hook_event_name){case"SessionStart":i="NOTIFICATION",s="Session started",t.source=e.source,t.transcript_path=e.transcript_path,t.model=e.model;break;case"UserPromptSubmit":i="USER_PROMPT",s=e.prompt||"";break;case"PreToolUse":i="NOTIFICATION",s="PreToolUse observed",t.tool_name=e.tool_name,t.tool_input=e.tool_input,t.tool_use_id=e.tool_use_id,t.approval_status="observed_pre_tool",t.requires_user_action=!1;break;case"PermissionRequest":i="NOTIFICATION",s="PermissionRequest observed",t.tool_name=e.tool_name,t.tool_input=e.tool_input,t.permission_mode=e.permission_mode,t.turn_id=e.turn_id,t.requires_user_action=!0;break;case"PostToolUse":i="TOOL_USE",s=JSON.stringify({tool_name:e.tool_name,tool_input:e.tool_input,tool_response:e.tool_response}),t.tool_name=e.tool_name,t.tool_input=e.tool_input,t.tool_use_id=e.tool_use_id;break;case"Stop":i="ASSISTANT_RESPONSE",s=e.last_assistant_message||"";break;default:i="NOTIFICATION",s=`Hook: ${e.hook_event_name}`}return{session_id:e.session_id,hook_event_name:e.hook_event_name,type:i,source:"DESKTOP",content:s,metadata:t}}onEvent(e){this.eventHandler=e}async start(){return new Promise((e,t)=>{try{this.server=this.app.listen(0,"localhost",()=>{let i=this.server.address();this.assignedPort=i.port,n.info(`HTTP API listening on http://localhost:${this.assignedPort}`),this.writePortFile(this.assignedPort),this.startPortFileKeepalive(),e(this.assignedPort)}),this.server.on("error",i=>{n.error("HTTP server error:",i),t(i)})}catch(i){t(i)}})}portFilePath(){return this.tmuxSession?Ee.join(Te.tmpdir(),`codevibe-codex-${this.tmuxSession}.port`):null}writePortFile(e){let t=this.portFilePath();if(!t){n.warn("No CODEVIBE_CODEX_TMUX_SESSION set, skipping port file");return}try{x.writeFileSync(t,e.toString()),n.info(`Port file written: ${t} -> ${e}`)}catch(i){n.error(`Failed to write port file: ${t}`,i)}}removePortFile(){let e=this.portFilePath();if(e)try{x.existsSync(e)&&(x.unlinkSync(e),n.info(`Port file removed: ${e}`))}catch(t){n.warn(`Failed to remove port file: ${e}`,t)}}startPortFileKeepalive(){this.portRefreshTimer&&(clearInterval(this.portRefreshTimer),this.portRefreshTimer=null);let e=Number(process.env.CODEVIBE_PORTFILE_REFRESH_MS),t=Number.isFinite(e)&&e>0?Math.min(ot,Math.max(nt,e)):rt;this.portRefreshTimer=setInterval(()=>this.refreshPortFile(),t)}refreshPortFile(){let e=this.portFilePath();if(e)try{let t=new Date;x.utimesSync(e,t,t)}catch(t){t?.code==="ENOENT"?this.writePortFile(this.assignedPort):n.warn(`Port-file refresh failed: ${e}`,t)}}async stop(){return this.portRefreshTimer&&(clearInterval(this.portRefreshTimer),this.portRefreshTimer=null),this.removePortFile(),new Promise(e=>{this.server?this.server.close(()=>{n.info("HTTP API stopped"),e()}):e()})}};var q=class{constructor(e={}){this.pendingBySession=new Map;this.expiryMs=e.expiryMs??1e4,this.minFuzzyEchoLength=e.minFuzzyEchoLength??16,this.minFuzzyEchoRatio=e.minFuzzyEchoRatio??.35,this.now=e.now??Date.now}track(e,t){let i=this.normalize(t);if(!i)return;let s=this.validEntries(e);s.push({normalized:i,timestamp:this.now()}),this.pendingBySession.set(e,s)}forget(e,t){let i=this.normalize(t);if(!i)return;let s=!1,r=this.validEntries(e).filter(o=>!s&&o.normalized===i?(s=!0,!1):!0);this.replaceEntries(e,r)}consumeIfDuplicate(e,t){let i=this.normalize(t);if(!i)return null;let s=null,r=this.validEntries(e).filter(o=>{if(!s){let l=this.matchType(o.normalized,i);if(l)return s={matchType:l,originalLength:o.normalized.length,echoLength:i.length},!1}return!0});return this.replaceEntries(e,r),s}validEntries(e){let t=this.now()-this.expiryMs;return(this.pendingBySession.get(e)||[]).filter(i=>i.timestamp>=t)}replaceEntries(e,t){t.length>0?this.pendingBySession.set(e,t):this.pendingBySession.delete(e)}matchType(e,t){if(e===t)return"exact";let i=t.length>=this.minFuzzyEchoLength,s=t.length/e.length>=this.minFuzzyEchoRatio;return i&&s&&e.endsWith(t)?"suffix":null}normalize(e){return e.replace(/\s+/g," ").trim()}};var Re=P(require("crypto")),Ce=P(require("fs")),Oe=P(require("https")),H=P(require("os")),Ae=P(require("path")),at="G-GS74YEQTB8",lt="lAfOF6OxRzSQ-NsLBRjhAg",pt="www.google-analytics.com",ct=`/mp/collect?measurement_id=${at}&api_secret=${lt}`,xe=800;function dt(){try{let c=Ae.resolve(__dirname,"..","package.json"),e=Ce.readFileSync(c,"utf-8"),t=JSON.parse(e);if(typeof t.version=="string"&&t.version.length>0&&t.version.length<30)return t.version}catch{}return"unknown"}var ut=dt();function ht(){let c=typeof process.getuid=="function"?process.getuid():0;return Re.createHash("sha256").update(`${H.hostname()}-${c}`).digest("hex").substring(0,36)}function mt(c){if(!c)return"";let e=H.homedir(),t=c.replace(/[\n\r\t]/g," ");if(e&&e.length>0){let i=e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");t=t.replace(new RegExp(i,"g"),"~")}return t=t.replace(/\/Users\/[^/\s"'`]+/g,"/Users/<user>").replace(/\/home\/[^/\s"'`]+/g,"/home/<user>").replace(/[^\x20-\x7E]/g,""),t.trim().substring(0,100)}function v(c,e){return new Promise(t=>{let i,s=!1,r=()=>{s||(s=!0,clearTimeout(o),t())},o=setTimeout(()=>{try{i?.destroy()}catch{}r()},xe);typeof o.unref=="function"&&o.unref();try{let l={...e};typeof l.error_message=="string"&&(l.error_message=mt(l.error_message));let a=JSON.stringify({client_id:ht(),events:[{name:c,params:{agent:"codex",plugin_version:ut,platform:process.platform,source:process.env.CODEVIBE_TELEMETRY_SOURCE||"production",...l}}]});i=Oe.request({hostname:pt,path:ct,method:"POST",headers:{"Content-Type":"application/json"},timeout:xe},p=>{p.resume(),p.on("end",r),p.on("close",r),p.on("error",r)}),i.on("error",r),i.on("timeout",()=>{try{i?.destroy()}catch{}r()}),i.on("close",r),i.write(a),i.end()}catch{r()}})}var W=class{constructor(){this.originated=new Set;this.externallyResolved=new Set}markOriginated(e){this.originated.add(e)}clearOriginated(e){this.originated.delete(e)}getOriginated(){return new Set(this.originated)}getExternallyResolved(){return new Set(this.externallyResolved)}isExternallyResolved(e){return this.externallyResolved.has(e)}dispatch(e){let{gateId:t}=e;return this.originated.has(t)?(this.originated.delete(t),this.externallyResolved.add(t),"self_echo"):(this.externallyResolved.add(t),"foreign")}clear(){this.originated.clear(),this.externallyResolved.clear()}};function ke(c){return`[CodeVibe] Decision recorded on another device: ${c} \u2014 type any number to dismiss the open AskUserQuestion`}var Ne=(0,te.promisify)(G.exec),ft=(0,te.promisify)(G.execFile),gt="/quit",D="CODEVIBE_CODEX_TMUX_SESSION",Z="CODEVIBE_CODEX_DAEMON_RECOVERY",yt=1e3,vt=600*1e3,St=30*1e3,bt=/^(?:[a-z0-9]|esc|escape)$/;async function Pt(c,e){let t=async(i,s)=>{try{await Ne(i)}catch(r){n.warn("tmux send-keys failed during self-terminate",{sessionName:c,label:s,error:String(r)})}};await t(`tmux send-keys -t "${c}" C-c`,"ctrl-c"),await new Promise(i=>setTimeout(i,200)),await t(`tmux send-keys -t "${c}" -l "${e}"`,"quit-text"),await new Promise(i=>setTimeout(i,500)),await t(`tmux send-keys -t "${c}" Enter`,"enter")}function ie(c){let e=c.split(`
13
- `),t=o=>/^[\s>]*\d+\.\s/.test(o),i=-1;for(let o=e.length-1;o>=0;o-=1)if(/^\s*\$\s+/.test(e[o])){i=o;break}if(i===-1)return;let s=e[i].trim().match(/^\$\s+(.+)$/);if(!s?.[1]?.trim())return;let r=[s[1].trim()];for(let o=i+1;o<e.length;o+=1){let l=e[o];if(t(l)||/^\s*$/.test(l)||/^\s*\$\s+/.test(l))break;r.push(l.trim())}return r.length>0?r:void 0}function Me(c){let e=ie(c);if(!e)return;let t=e.join(" ").trim();return t.length>0?t:void 0}var V=class c{constructor(){this.sessionState=null;this.unsubscribe=null;this.sessionKey=null;this.sessionIsEncrypted=!1;this.sessionKeyGen=null;this.e1PromptRaises=new Map;this.e1NeedsReRaise=new Set;this.e1RetirementOrphans=new Map;this.e1ContentKeyIndex=new Map;this.e1TtlRefreshTimer=null;this.toolActivity=null;this.toolActivityStartPromise=null;this.toolActivityLegacyRecorders=new Map;this.pendingInteractivePrompt=null;this.queuedInteractivePrompts=[];this.isInitializingSession=!1;this.bufferedLogEntries=[];this.logEntryChain=Promise.resolve();this.hooksActive=!1;this.hooksCompatibilityNoticeSent=!1;this.resolvedApprovalDedupeKeys=new Map;this.subscribedSessionId=null;this.subscribedSessionState=null;this.terminalInputChain=Promise.resolve();this.terminalInputBlockedStates=new WeakSet;this.mobilePromptDeduper=new q({expiryMs:1e4});this.launchSessionInitPromise=null;this.sessionStartedInitPromise=null;this.bootstrapSessionId=null;this.recoveryBootstrapOwnerBound=!1;this.resumeBackendSession=d.resumeOrCreateSession;this.sessionBootstrapFailureCount=0;this.sessionBootstrapRetryAt=0;this.retiredSessionRecoveryPromise=null;this.lifecycleGeneration=0;this.isStopping=!1;this.tmuxLifecycleTimer=null;this.tmuxLifecycleCheckInFlight=!1;this.tmuxLifecycleObservedAlive=!1;this.v1Bridge=new W;this.orchestrationClient=null;this.userDecisionUnsubscribe=null;this.httpApi=new z,this.sessionWatcher=new L,this.approvalDetector=new B,this.promptResponder=new K,this.tmuxPaneObserver=new U}static{this.E1_TTL_REFRESH_MS=300*1e3}async start(){n.info("Starting CodeVibe Codex companion server",{environment:(0,d.getEnvironment)()}),this.appSyncClient=new d.AppSyncClient,await this.appSyncClient.authenticateWithStoredTokens()||(n.error('Authentication failed. Run "codevibe login" first.'),console.error('Not authenticated. Run "codevibe login" to sign in.'),process.exit(1)),n.info("Authenticated successfully",{userId:this.appSyncClient.getCurrentUserId(),email:this.appSyncClient.getCurrentUserEmail()}),await(0,d.registerDeviceEncryptionKey)(this.appSyncClient,n),(0,d.startDeviceKeyWatcher)(this.appSyncClient,n),(0,d.pushDetectedAgents)(this.appSyncClient,n).catch(i=>{n.warn("Failed to update available agents (non-fatal)",{error:i?.message})});try{let i=await this.appSyncClient.sweepOrphanSessions({agentType:"CODEX"});i>0&&n.info("Orphan sweep: marked stale Codex sessions INACTIVE",{swept:i})}catch(i){n.warn("Orphan sweep failed, continuing startup",{error:i instanceof Error?i.message:String(i)})}this.httpApi.onEvent(this.handleEventFromHook.bind(this));let t=await this.httpApi.start();n.info("HTTP API started for hooks",{port:t}),this.startTmuxLifecycleMonitor(),await this.createLaunchSession(),this.setupEventHandlers(),this.sessionWatcher.start(),n.info("CodeVibe Codex companion server started")}async createLaunchSession(){if(this.isStopping)return;if(process.env[Z]==="1"){n.info("Recovery daemon deferring backend bootstrap until the native rollout is known");return}let e=this.lifecycleGeneration,t=process.env.CODEVIBE_CODEX_TMUX_SESSION;if(!t){n.warn("No CODEVIBE_CODEX_TMUX_SESSION \u2014 skipping launch session");return}let i;this.launchSessionInitPromise=new Promise(s=>{i=s});try{let s=process.env.CODEX_WORKING_DIRECTORY||process.cwd(),r=this.getOrCreateBootstrapSessionId(),o=this.appSyncClient.getCurrentUserId();n.info("Creating launch session",{sessionId:r,projectPath:s});let l=!1;try{let a=await(0,d.resumeOrCreateSession)({sessionId:r,userId:o,agentType:d.AgentType.CODEX,projectPath:s,metadata:{launchSession:!0}},this.appSyncClient,n);if(r=a.sessionId,!this.isLifecycleCurrent(e)){await this.retireCancelledBootstrap(r,"launch_session");return}this.sessionKey=a.sessionKey,this.sessionKeyGen=a.sessionKeyGen,this.sessionIsEncrypted=!!a.sessionKey,l=!0,this.sessionState={sessionId:r,userId:o,projectPath:s,cwd:s,createdAt:new Date,subscriptionActive:!1,metadata:{launchSession:!0},encryptionSnapshot:{sessionKey:a.sessionKey,sessionIsEncrypted:!!a.sessionKey},codexSessionId:t,codexLogFile:void 0},this.clearSessionBootstrapFailure(),this.bootstrapSessionId=null,await v("daemon_init_step_completed",{step:"session_resume_or_create",path:"launch_session"})}catch(a){if(!this.isLifecycleCurrent(e)){let p=this.authoritativeSessionIdFromError(a)??r;await this.retireCancelledBootstrap(p,"launch_session_error");return}await v("daemon_init_step_failed",{step:"session_resume_or_create",path:"launch_session",error_class:a?.name||"Error",error_message:a?.message||String(a)}),a?.code==="ENCRYPTED_SESSION_NO_KEY"&&(this.sessionIsEncrypted=!0,this.sessionKey=null),this.bindBootstrapIdentityFromError(a),this.recordSessionBootstrapFailure(a,"launch_session"),n.error("Failed to create/resume launch session (non-fatal)",{error:a})}if(!l)return;await v("daemon_init_step_completed",{step:"session_state_set",path:"launch_session"});try{this.subscribeToMobileEvents(r)?await v("daemon_init_step_completed",{step:"subscribe_mobile_events",path:"launch_session"}):await v("daemon_init_step_failed",{step:"subscribe_mobile_events",path:"launch_session",error_class:"SubscriptionSetupFailed",error_message:"subscribeToMobileEvents returned false"})}catch(a){await v("daemon_init_step_failed",{step:"subscribe_mobile_events",path:"launch_session",error_class:a?.name||"Error",error_message:a?.message||String(a)}),n.error("Failed to subscribe to mobile events for launch session",{error:a})}try{await this.appSyncClient.startHeartbeat(r)?await v("daemon_init_step_completed",{step:"heartbeat_start",path:"launch_session"}):(await v("daemon_init_step_failed",{step:"heartbeat_start",path:"launch_session",error_class:"InitialHeartbeatNotPersisted",error_message:"Backend did not confirm the initial heartbeat mutation"}),n.error("Initial launch-session heartbeat was not persisted",{sessionId:r}))}catch(a){await v("daemon_init_step_failed",{step:"heartbeat_start",path:"launch_session",error_class:a?.name||"Error",error_message:a?.message||String(a)}),n.error("Failed to start heartbeat for launch session",{error:a})}try{await this.subscribeToOnApplyUserDecision(r)}catch(a){n.error("Failed to subscribe to onApplyUserDecision for launch session (non-fatal)",{error:a})}this.startMobileEndWatcher(r),n.info("Launch session created",{sessionId:r})}finally{i()}}encryptForEmit(e,t,i,s){let r=this.sessionState;if(!r||r.sessionId!==s||!r.encryptionSnapshot)return n.warn("Dropping event from stale or unresolved session generation (#638)",{type:i,sessionId:s,currentSessionId:r?.sessionId}),null;let{sessionKey:o,sessionIsEncrypted:l}=r.encryptionSnapshot,a=(0,d.encryptForEmit)(o,l,e,t);return a===null&&n.error("No session key for ENCRYPTED session \u2014 dropping event (fail-closed, #638)",{type:i,sessionId:s}),a}newE1Raise(e,t,i){let{record:s}=(0,d.newPromptRaise)({sessionId:e,producerKind:"PLUGIN_APPROVAL",mobileActionable:t,agentType:"CODEX",...i!==void 0&&{title:i}});return this.e1PromptRaises.set(s.promptId,{record:s}),this.e1TtlRefreshTimer||this.startE1TtlRefresh(),s}stashE1RaiseContent(e,t,i){let s=this.e1PromptRaises.get(e);s&&(s.contentPlain=t,s.metadataPlain=i)}async emitSuppressedPromptCarrier(e,t){let i="\u26A0\uFE0F A prompt is waiting in your desktop terminal, but its options could not be shown here. Please answer it on your desktop.",s=this.encryptForEmit(i,{e1SuppressedPrompt:!0},d.EventType.NOTIFICATION,e);return s?(await this.appSyncClient.createEvent({sessionId:e,type:d.EventType.NOTIFICATION,source:d.EventSource.DESKTOP,content:s.content,metadata:s.metadata,...(0,d.raiseFieldsFromRecord)(t),notificationText:i,timestamp:(0,d.prepareEventTimestamp)({orderingKey:e}),isEncrypted:s.isEncrypted?!0:void 0}),n.info("E1: emitted suppressed-prompt badge carrier (mobileActionable=false)",{sessionId:e,promptId:t.promptId}),!0):!1}raiseSuppressedBadge(e,t){if(t){let s=`${e}::${t}`,r=this.e1ContentKeyIndex.get(s);if(r&&this.e1PromptRaises.has(r))return;r&&this.e1ContentKeyIndex.delete(s)}let i=this.newE1Raise(e,!1);t&&this.e1ContentKeyIndex.set(`${e}::${t}`,i.promptId),this.emitSuppressedPromptCarrier(e,i).catch(s=>{n.error("E1: failed to emit suppressed-prompt badge carrier",{sessionId:e,promptId:i.promptId,error:s instanceof Error?s.message:String(s)})})}snapshotOpenE1(e){return[...this.e1PromptRaises.values()].filter(t=>t.record.sessionId===e).map(t=>({record:{...t.record},...t.contentPlain!==void 0&&{contentPlain:t.contentPlain},...t.metadataPlain!==void 0&&{metadataPlain:t.metadataPlain}}))}parkRetirementOrphans(e){for(let t of this.snapshotOpenE1(e))this.e1RetirementOrphans.set(t.record.promptId,t)}async drainRetirementOrphans(e){if(this.e1RetirementOrphans.size!==0){n.info("E1 (\xA76a-4): re-homing parked retirement-orphan prompts onto replacement session",{replacementSessionId:e,count:this.e1RetirementOrphans.size});for(let[t,i]of[...this.e1RetirementOrphans]){let r=this.e1PromptRaises.get(t)??{record:{...i.record},...i.contentPlain!==void 0&&{contentPlain:i.contentPlain},...i.metadataPlain!==void 0&&{metadataPlain:i.metadataPlain}};if(r.record.sessionId=e,this.e1PromptRaises.set(t,r),!await this.reRaiseOneE1(e,r,{untilAck:!0}))break;this.e1RetirementOrphans.delete(t)}this.e1TtlRefreshTimer||this.startE1TtlRefresh()}}isReRaiseTargetCurrent(e){return!this.isStopping&&this.sessionState!==null&&this.sessionState.sessionId===e}async reRaiseOneE1(e,t,i){let s=t.record,r=s.mobileActionable&&t.contentPlain!==void 0,o=i?.untilAck??!1,l=4,a=4e3;for(let p=1;o||p<=l;p++){if(o&&!this.isReRaiseTargetCurrent(e))return this.e1NeedsReRaise.add(s.promptId),n.warn("E1: retirement re-raise aborted (stopping/superseded) \u2014 parked",{sessionId:e,promptId:s.promptId}),!1;try{if(r){let u=this.encryptForEmit(t.contentPlain,t.metadataPlain??{},d.EventType.INTERACTIVE_PROMPT,e);if(!u)return!1;await this.appSyncClient.createEvent({sessionId:e,type:d.EventType.INTERACTIVE_PROMPT,source:d.EventSource.DESKTOP,content:u.content,metadata:u.metadata,...(0,d.raiseFieldsFromRecord)(s),timestamp:(0,d.prepareEventTimestamp)({orderingKey:e}),...u.isEncrypted?{isEncrypted:!0}:{}})}else if(s.mobileActionable=!1,!await this.emitSuppressedPromptCarrier(e,s))return!1;return this.e1NeedsReRaise.delete(s.promptId),n.info("E1: re-raised prompt onto replacement session",{sessionId:e,promptId:s.promptId,actionable:r}),!0}catch(u){if(!o&&p===l)return this.e1NeedsReRaise.add(s.promptId),n.error("E1: re-raise unacked after backoff \u2014 parked for retry-until-ack",{sessionId:e,promptId:s.promptId,error:u instanceof Error?u.message:String(u)}),!1;if(o){let h=Date.now()+Math.min(250*2**(p-1),a);for(;Date.now()<h&&this.isReRaiseTargetCurrent(e);)await new Promise(m=>setTimeout(m,Math.min(50,h-Date.now())))}else await new Promise(h=>setTimeout(h,250*p))}}return!1}async drainPendingE1ReRaises(){if(this.e1NeedsReRaise.size!==0)for(let e of[...this.e1NeedsReRaise]){let t=this.e1PromptRaises.get(e);if(!t){this.e1NeedsReRaise.delete(e);continue}!this.sessionState||t.record.sessionId!==this.sessionState.sessionId||await this.reRaiseOneE1(t.record.sessionId,t)}}async refreshE1Ttls(){await this.drainPendingE1ReRaises();let e=this.sessionState?.sessionId;if(!e)return;let t=[...this.e1PromptRaises.values()].filter(i=>i.record.sessionId===e).map(i=>i.record.promptId);if(t.length!==0)try{let i=await this.appSyncClient.refreshOpenPromptTtl(t);for(let s of i){let r=this.e1PromptRaises.get(s);r&&r.record.sessionId===e&&await this.reRaiseOneE1(e,r)}}catch(i){n.warn("E1: TTL-refresh tick failed (non-fatal, retries next tick)",{error:i instanceof Error?i.message:String(i)})}}startE1TtlRefresh(){this.e1TtlRefreshTimer&&clearInterval(this.e1TtlRefreshTimer),this.e1TtlRefreshTimer=setInterval(()=>{this.refreshE1Ttls()},c.E1_TTL_REFRESH_MS)}stopE1TtlRefresh(){this.e1TtlRefreshTimer&&(clearInterval(this.e1TtlRefreshTimer),this.e1TtlRefreshTimer=null)}isExpectedSessionStateCurrent(e){return!this.isStopping&&this.sessionState===e&&!this.terminalInputBlockedStates.has(e)}sendSessionPinnedInput(e,t){if(!this.isExpectedSessionStateCurrent(e)||this.terminalInputBlockedStates.has(e))return Promise.resolve(!1);let i=this.terminalInputChain.then(async()=>!this.isExpectedSessionStateCurrent(e)||this.terminalInputBlockedStates.has(e)?!1:this.promptResponder.sendInput(e.sessionId,t));return this.terminalInputChain=i.then(()=>{},()=>{}),i}async handleEventFromHook(e){let{session_id:t,hook_event_name:i,type:s,content:r,metadata:o}=e;if(this.hooksActive=!0,n.info("[Hooks] Received event",{sessionId:t,hookEvent:i,type:s,contentLength:r?.length}),i==="SessionStart"){if(this.launchSessionInitPromise&&await this.launchSessionInitPromise,await this.sessionWatcher.bindToSessionTranscript(o?.transcript_path,t),this.sessionState)n.info("[Hooks] SessionStart \u2014 launch session already exists, updating codexSessionId",{existingSessionId:this.sessionState.sessionId,codexSessionId:t}),this.sessionState.codexSessionId=t,this.sessionState.metadata={...this.sessionState.metadata,codexSessionId:t,cliVersion:o?.model||"unknown",modelProvider:o?.model||"unknown",launchSession:void 0},this.appSyncClient.updateSession({sessionId:this.sessionState.sessionId,metadata:this.sessionState.metadata}).catch(p=>n.warn("Failed to update session metadata",{error:p})),await this.startTmuxObserverWithBeacon("session_start_existing"),await this.ensureToolActivityStarted(t);else{let p={id:t,timestamp:new Date().toISOString(),cwd:o?.cwd||process.cwd(),originator:"hook",cli_version:o?.model||"unknown",instructions:null,source:o?.source||"startup",model_provider:o?.model||"unknown"};await this.ensureSessionStarted(p)}return}if(!this.sessionState){n.warn("[Hooks] Hook event for un-bootstrapped session \u2014 self-healing via session bootstrap (#638)",{hookEvent:i,sessionId:t});let p={id:t,timestamp:new Date().toISOString(),cwd:o?.cwd||process.cwd(),originator:"hook",cli_version:o?.model||"unknown",instructions:null,source:o?.source||"startup",model_provider:o?.model||"unknown"};if(await this.ensureSessionStarted(p),!this.sessionState){n.warn("[Hooks] Session still not initialized after self-heal, dropping event",{hook_event_name:i});return}}let l=this.sessionState,a=l.sessionId;if(s==="USER_PROMPT"&&r){let p=this.consumeRecentMobilePrompt(a,r);if(p){n.info("[Hooks] Skipping duplicate USER_PROMPT from mobile",{sessionId:a,matchType:p.matchType,originalLength:p.originalLength,echoLength:p.echoLength});return}}if(i==="PreToolUse"){n.debug("[Hooks] PreToolUse observed; AppSync emission suppressed",{toolName:o?.tool_name||"unknown",sessionId:a});return}if(i==="PermissionRequest"){await this.handlePermissionRequestHook(e,l);return}if(i==="PostToolUse"){if(this.toolActivity?.isActive()&&await this.toolActivity.observeCall({callId:o?.tool_use_id??o?.call_id??o?.callId,toolName:o?.tool_name,toolInput:o?.tool_input})!=="closed")return;let p=this.encryptForEmit(r,o,d.EventType.TOOL_USE,a);if(!p)return;let u=p.content,h=p.metadata,m=p.isEncrypted,g=o?.tool_use_id??o?.call_id??o?.callId,f=typeof g=="string"&&g.length>0&&!!this.sessionKey,y=f?this.getToolActivityLegacyRecorder(a):void 0;y&&f&&y.markLegacyInFlight(g);try{let w=await this.appSyncClient.createEvent({sessionId:a,type:d.EventType.TOOL_USE,source:d.EventSource.DESKTOP,content:u,metadata:h,isEncrypted:m,timestamp:(0,d.prepareEventTimestamp)({orderingKey:a})});y&&f&&((0,d.isPersistedEventResult)(w)?await y.finalizeLegacyOwned([g]):y.clearLegacyInFlight(g))}catch(w){throw y&&f&&y.clearLegacyInFlight(g),w}return}if(s==="ASSISTANT_RESPONSE"||s==="USER_PROMPT"){if(s==="ASSISTANT_RESPONSE"&&this.sessionWatcher.getBindSource()==="authoritative"&&this.sessionWatcher.getAuthoritativeSessionId()===t){n.debug("[Hooks] Suppressing Stop final \u2014 JSONL watcher authoritatively bound to this session (Bug #2)");return}s==="ASSISTANT_RESPONSE"&&n.info("[Hooks] Emitting Stop final as backstop \u2014 watcher not authoritatively bound",{bindSource:this.sessionWatcher.getBindSource()});let p=s==="ASSISTANT_RESPONSE"?d.EventType.ASSISTANT_RESPONSE:d.EventType.USER_PROMPT,u=this.encryptForEmit(r,void 0,p,a);if(!u)return;await this.appSyncClient.createEvent({sessionId:a,type:p,source:d.EventSource.DESKTOP,content:u.content,isEncrypted:u.isEncrypted,timestamp:(0,d.prepareEventTimestamp)({orderingKey:a})});return}}mapToolName(e){return{shell_command:"Bash",shell:"Bash",apply_patch:"Edit",create_file:"Write",read_file:"Read"}[e]||e}trackMobilePrompt(e,t){this.mobilePromptDeduper.track(e,t),n.debug("Tracking mobile prompt for USER_PROMPT echo deduplication",{sessionId:e,promptLength:t.trim().length})}forgetMobilePrompt(e,t){this.mobilePromptDeduper.forget(e,t)}consumeRecentMobilePrompt(e,t){return this.mobilePromptDeduper.consumeIfDuplicate(e,t)}setupEventHandlers(){this.sessionWatcher.on("session-started",async e=>{try{if(this.launchSessionInitPromise&&await this.launchSessionInitPromise,this.sessionState){n.info("[JSONL] Session already active, skipping",{currentSessionId:this.sessionState.sessionId,codexSessionId:e.id}),await this.ensureToolActivityStarted(e.id);return}await this.ensureSessionStarted(e)}catch(t){n.error("[JSONL] session-started handler failed (non-fatal) \u2014 next event retries",{codexSessionId:e?.id,error:String(t)})}}),this.sessionWatcher.on("log-entry",e=>{this.enqueueLogEntry(e)}),this.approvalDetector.on("approval-pending",async e=>{try{await this.handleApprovalPending(e)}catch(t){n.error("approval-pending handler failed (non-fatal)",{error:String(t)})}}),this.tmuxPaneObserver.on("prompt-candidate",async e=>{try{await this.handleTmuxPromptCandidate(e.snapshot)}catch(t){n.error("prompt-candidate handler failed (non-fatal)",{error:String(t)})}}),this.tmuxPaneObserver.on("observer-error",e=>{n.debug("Tmux pane observer error",{error:e})}),this.sessionWatcher.on("error",e=>{n.error("Session watcher error:",e)})}async ensureSessionStarted(e,t){if(this.isStopping||(this.launchSessionInitPromise&&await this.launchSessionInitPromise,this.isStopping)||this.sessionState)return;process.env[Z]==="1"&&await this.bindRecoveryBootstrapIdentity(e.id);let i=this.sessionBootstrapRetryAt-Date.now();if(i>0){n.warn("Session bootstrap is in retry backoff; hook will be retried later",{codexSessionId:e.id,retryDelayMs:i,failureCount:this.sessionBootstrapFailureCount});return}if(this.sessionStartedInitPromise){try{await this.sessionStartedInitPromise}catch{}return}let s=this.handleSessionStarted(e,t);this.sessionStartedInitPromise=s.catch(()=>{});try{await s}finally{this.sessionStartedInitPromise=null}}async handleSessionStarted(e,t){if(this.isStopping)return;let i=this.lifecycleGeneration;n.info("Handling new Codex session",{codexSessionId:e.id}),this.isInitializingSession=!0,this.bufferedLogEntries=[],this.sessionState&&await this.endActiveSession("new-codex-session-started");let s=process.env.CODEX_WORKING_DIRECTORY||e.cwd||process.cwd(),r=this.getOrCreateBootstrapSessionId(),o=this.appSyncClient.getCurrentUserId(),l={codexSessionId:e.id,cliVersion:e.cli_version,modelProvider:e.model_provider},a={sessionKey:null,sessionIsEncrypted:!0};try{let p={sessionId:r,userId:o,agentType:d.AgentType.CODEX,projectPath:s,metadata:l},u=await this.resumeOrCreateBootstrapSession(p);if(r=u.sessionId,!this.isLifecycleCurrent(i)){this.isInitializingSession=!1,await this.retireCancelledBootstrap(r,"session_started");return}this.sessionKey=u.sessionKey,this.sessionKeyGen=u.sessionKeyGen,this.sessionIsEncrypted=!!u.sessionKey,a={sessionKey:u.sessionKey,sessionIsEncrypted:!!u.sessionKey},await v("daemon_init_step_completed",{step:"session_resume_or_create",path:"session_started"})}catch(p){if(this.isInitializingSession=!1,!this.isLifecycleCurrent(i)){let u=this.authoritativeSessionIdFromError(p)??r;await this.retireCancelledBootstrap(u,"session_started_error");return}throw await v("daemon_init_step_failed",{step:"session_resume_or_create",path:"session_started",error_class:p?.name||"Error",error_message:p?.message||String(p)}),p?.code==="ENCRYPTED_SESSION_NO_KEY"&&(this.sessionIsEncrypted=!0,this.sessionKey=null),this.bindBootstrapIdentityFromError(p),this.recordSessionBootstrapFailure(p,"session_started"),n.error("Failed to create/resume session:",p),p}try{this.sessionState={sessionId:r,userId:o,projectPath:s,cwd:e.cwd,createdAt:new Date,subscriptionActive:!1,metadata:l,encryptionSnapshot:a,codexSessionId:e.id,codexLogFile:this.sessionWatcher.getActiveLogFile()||void 0},this.clearSessionBootstrapFailure(),this.bootstrapSessionId=null,this.recoveryBootstrapOwnerBound=!1,await v("daemon_init_step_completed",{step:"session_state_set",path:"session_started"})}catch(p){await v("daemon_init_step_failed",{step:"session_state_set",path:"session_started",error_class:p?.name||"Error",error_message:p?.message||String(p)}),n.error("Failed to set session state:",p)}await this.ensureToolActivityStarted(e.id);try{this.subscribeToMobileEvents(r)?await v("daemon_init_step_completed",{step:"subscribe_mobile_events",path:"session_started"}):await v("daemon_init_step_failed",{step:"subscribe_mobile_events",path:"session_started",error_class:"SubscriptionSetupFailed",error_message:"subscribeToMobileEvents returned false"})}catch(p){await v("daemon_init_step_failed",{step:"subscribe_mobile_events",path:"session_started",error_class:p?.name||"Error",error_message:p?.message||String(p)}),n.error("Failed to subscribe to mobile events:",p)}if(t)try{await t()}catch(p){n.error("E1: pre-heartbeat re-raise hook failed (non-fatal)",{sessionId:r,error:p instanceof Error?p.message:String(p)})}try{await this.drainRetirementOrphans(r)}catch(p){n.error("E1: retirement-orphan drain failed (non-fatal)",{sessionId:r,error:p instanceof Error?p.message:String(p)})}try{await this.appSyncClient.startHeartbeat(r)?await v("daemon_init_step_completed",{step:"heartbeat_start",path:"session_started"}):(await v("daemon_init_step_failed",{step:"heartbeat_start",path:"session_started",error_class:"InitialHeartbeatNotPersisted",error_message:"Backend did not confirm the initial heartbeat mutation"}),n.error("Initial session heartbeat was not persisted",{sessionId:r}))}catch(p){await v("daemon_init_step_failed",{step:"heartbeat_start",path:"session_started",error_class:p?.name||"Error",error_message:p?.message||String(p)}),n.error("Failed to start heartbeat:",p)}try{await this.subscribeToOnApplyUserDecision(r)}catch(p){n.error("Failed to subscribe to onApplyUserDecision (non-fatal)",{error:p})}this.startMobileEndWatcher(r);try{await this.flushBufferedLogEntries(),await v("daemon_init_step_completed",{step:"flush_buffered_entries",path:"session_started"})}catch(p){await v("daemon_init_step_failed",{step:"flush_buffered_entries",path:"session_started",error_class:p?.name||"Error",error_message:p?.message||String(p)}),n.error("Failed to flush buffered log entries:",p),this.bufferedLogEntries=[]}await this.startTmuxObserverWithBeacon("session_started"),this.isInitializingSession=!1}getOrCreateBootstrapSessionId(){return this.bootstrapSessionId||(this.bootstrapSessionId=k.mintBackendSessionId()),this.bootstrapSessionId}async bindRecoveryBootstrapIdentity(e){if(!(process.env[Z]!=="1"||this.bootstrapSessionId||this.sessionState||!e))try{let t=await this.buildToolActivityIntegration().recordedBackendSessionId(e);if(!t){n.warn("Recovery daemon found no durable backend owner; a new session will be created",{rolloutId:e});return}if(!/^codex-[A-Za-z0-9_-]{1,180}$/.test(t)){n.warn("Recovery daemon ignored invalid durable backend owner",{rolloutId:e});return}this.bootstrapSessionId=t,this.recoveryBootstrapOwnerBound=!0,n.info("Recovery daemon reclaiming existing backend session",{rolloutId:e,sessionId:t})}catch(t){n.warn("Recovery daemon could not read the durable rollout owner; a new session will be created",{rolloutId:e,error:t instanceof Error?t.message:String(t)})}}isSessionIdReservedRejection(e){return(e instanceof Error?e.message:String(e)).includes("SESSION_ID_RESERVED")}async resumeOrCreateBootstrapSession(e){try{return await this.resumeBackendSession(e,this.appSyncClient,n)}catch(t){if(!this.recoveryBootstrapOwnerBound||!this.isSessionIdReservedRejection(t))throw t;return n.warn("Recovery discarded a permanently reserved pending create; retrying the durable rollout owner",{sessionId:e.sessionId}),this.resumeBackendSession(e,this.appSyncClient,n)}}startTmuxLifecycleMonitor(){if(this.tmuxLifecycleTimer)return;let e=process.env[D];e&&(this.tmuxLifecycleTimer=setInterval(()=>{this.checkTmuxLifecycle(e)},yt),this.tmuxLifecycleTimer.unref?.(),this.checkTmuxLifecycle(e))}async tmuxSessionExists(e){try{return await ft("tmux",["has-session","-t",e]),!0}catch{return!1}}async checkTmuxLifecycle(e){if(!(this.isStopping||this.tmuxLifecycleCheckInFlight)){this.tmuxLifecycleCheckInFlight=!0;try{if(await this.tmuxSessionExists(e)){this.tmuxLifecycleObservedAlive=!0;return}if(!this.tmuxLifecycleObservedAlive||this.isStopping)return;this.stopTmuxLifecycleMonitor(),n.info("Native Codex tmux session ended; stopping companion daemon",{tmuxSession:e}),this.stop().then(()=>process.exit(0),t=>{n.error("Failed to stop companion daemon after native session end",{error:t instanceof Error?t.message:String(t)}),process.exit(1)})}finally{this.tmuxLifecycleCheckInFlight=!1}}}stopTmuxLifecycleMonitor(){this.tmuxLifecycleTimer&&(clearInterval(this.tmuxLifecycleTimer),this.tmuxLifecycleTimer=null)}isLifecycleCurrent(e){return!this.isStopping&&this.lifecycleGeneration===e}authoritativeSessionIdFromError(e){let t=e?.authoritativeSessionId;return typeof t=="string"&&t.length>0?t:null}async retireCancelledBootstrap(e,t){this.appSyncClient.stopHeartbeat(e),this.appSyncClient.cleanupSubscription(e);try{await this.appSyncClient.updateSession({sessionId:e,status:d.SessionStatus.INACTIVE}),n.info("Cancelled bootstrap row marked INACTIVE",{sessionId:e,path:t})}catch(i){n.warn("Cancelled bootstrap row could not be marked INACTIVE",{sessionId:e,path:t,error:i instanceof Error?i.message:String(i)})}}bindBootstrapIdentityFromError(e){let t=e?.authoritativeSessionId;typeof t!="string"||t.length===0||(this.bootstrapSessionId=t,n.info("Retained authoritative backend identity from failed bootstrap",{sessionId:t}))}recoverRetiredBackendSession(e){if(this.retiredSessionRecoveryPromise)return this.retiredSessionRecoveryPromise;if(this.isStopping)return Promise.resolve();let t=(async()=>{if(this.launchSessionInitPromise&&await this.launchSessionInitPromise,this.sessionStartedInitPromise&&await this.sessionStartedInitPromise,this.isStopping)return;let i=this.sessionState;if(!i||i.sessionId!==e)return;let s={id:i.codexSessionId||e,timestamp:new Date().toISOString(),cwd:i.cwd||i.projectPath,originator:"codevibe-retired-session-recovery",cli_version:String(i.metadata?.cliVersion??"unknown"),instructions:null,source:"codevibe",model_provider:String(i.metadata?.modelProvider??"unknown")};n.warn("Backend retired live Codex session; creating replacement row",{sessionId:e,codexSessionId:s.id}),this.parkRetirementOrphans(e),await this.endActiveSession("backend-generation-retired"),this.bootstrapSessionId=null,this.clearSessionBootstrapFailure(),await this.ensureSessionStarted(s)})();return this.retiredSessionRecoveryPromise=t.finally(()=>{this.retiredSessionRecoveryPromise=null}),this.retiredSessionRecoveryPromise}recordSessionBootstrapFailure(e,t){this.sessionBootstrapFailureCount+=1;let i=e instanceof Error?e.message:String(e),r=i.includes("SESSION_LIMIT_EXCEEDED")?6e4:Math.min(5e3*2**(this.sessionBootstrapFailureCount-1),6e4);this.sessionBootstrapRetryAt=Date.now()+r,n.warn("Session bootstrap retry scheduled",{path:t,requestedSessionId:this.bootstrapSessionId,failureCount:this.sessionBootstrapFailureCount,delayMs:r,error:i})}clearSessionBootstrapFailure(){this.sessionBootstrapFailureCount=0,this.sessionBootstrapRetryAt=0}async flushBufferedLogEntries(){if(this.bufferedLogEntries.length===0)return;let e=this.bufferedLogEntries;this.bufferedLogEntries=[],n.info("Flushing buffered log entries after session initialization",{count:e.length,sessionId:this.sessionState?.sessionId});let t=Promise.resolve();for(let i of e)t=this.enqueueLogEntry(i);await t.catch(()=>{})}enqueueLogEntry(e){let t=this.logEntryChain.then(()=>this.handleLogEntry(e));return this.logEntryChain=t.catch(i=>{n.error("[JSONL] log entry handler failed",{type:e.type,err:String(i)})}),t}async handleLogEntry(e){if(!this.sessionState){if(this.isInitializingSession){this.bufferedLogEntries.push(e),n.debug("Buffering log entry until session initialization completes",{type:e.type,bufferedCount:this.bufferedLogEntries.length});return}n.warn("Received log entry but no active session");return}let t=this.sessionState,i=t.sessionId,{sessionKey:s,sessionIsEncrypted:r}=t.encryptionSnapshot;if(e.type==="response_item"&&e.payload){let f=e.payload.type;if(f==="function_call"||f==="custom_tool_call")this.approvalDetector.onToolCallStart(e.payload.call_id,e.payload.name,e.payload.arguments||e.payload.input||"");else if(f==="function_call_output"||f==="custom_tool_call_output"){let y=this.approvalDetector.getPendingCalls().find(T=>T.callId===e.payload.call_id),w=y?this.buildApprovalDedupeKey(this.buildApprovalPromptContextFromPendingCall(y)):void 0;if(this.approvalDetector.onToolCallComplete(e.payload.call_id),await this.clearResolvedInteractivePrompt(e.payload.call_id,w),!this.isExpectedSessionStateCurrent(t))return}}let o=ue(e,i);if(!o)return;o.timestamp=(0,d.prepareEventTimestamp)({orderingKey:i,agentClock:e.timestamp});let l=e.payload?.type,a=!1;if(this.toolActivity?.isActive()&&o.type===d.EventType.TOOL_USE){let f=l==="function_call"||l==="custom_tool_call",y=l==="function_call_output"||l==="custom_tool_call_output";if(f&&typeof e.payload?.call_id=="string"){let w=this.sessionWatcher.getActiveLogFile();w&&this.toolActivity.bindTranscript(w);let T=await this.toolActivity.observeCall({callId:e.payload.call_id,toolName:o.metadata?.toolName??o.metadata?.tool_name,toolInput:o.metadata?.toolInput??o.metadata?.tool_input,ts:e.timestamp});if(!this.isExpectedSessionStateCurrent(t)||T!=="closed")return;a=!0}if(y)return}if(this.toolActivity?.isActive()&&(l==="user_message"||l==="agent_message")&&(await this.toolActivity.flush().catch(f=>n.warn("[tool-activity] codex boundary flush failed",{sessionId:i,err:String(f)})),!this.isExpectedSessionStateCurrent(t)))return;let p=l==="function_call"||l==="function_call_output",u=o.type===d.EventType.USER_PROMPT||o.type===d.EventType.ASSISTANT_RESPONSE||p&&(o.type===d.EventType.TOOL_USE||o.type===d.EventType.INTERACTIVE_PROMPT);if(!this.hooksActive&&u&&(await this.emitHooksCompatibilityModeNotice(t),!this.isExpectedSessionStateCurrent(t)))return;if(this.hooksActive){if(o.type===d.EventType.USER_PROMPT){n.debug("[JSONL] Skipping USER_PROMPT \u2014 hooks deliver it",{type:o.type});return}if(p&&(o.type===d.EventType.TOOL_USE||o.type===d.EventType.INTERACTIVE_PROMPT)&&!a){n.debug("[JSONL] Skipping function_call \u2014 hooks deliver this",{type:o.type,tool:e.payload?.name});return}}if(o.type===d.EventType.USER_PROMPT&&o.source===d.EventSource.DESKTOP){let f=this.consumeRecentMobilePrompt(i,o.content);if(f){n.info("[JSONL] Skipping duplicate USER_PROMPT from mobile",{sessionId:i,matchType:f.matchType,originalLength:f.originalLength,echoLength:f.echoLength});return}}if(this.sessionState!==t){n.warn("[JSONL] Dropping event from stale session generation (#638)",{sessionId:i,currentSessionId:this.sessionState?.sessionId});return}let h=e.payload?.call_id,m=o.type===d.EventType.TOOL_USE&&typeof h=="string"&&h.length>0&&!!s,g=m?this.getToolActivityLegacyRecorder(i):void 0;g&&m&&g.markLegacyInFlight(h);try{let f=(0,d.encryptForEmit)(s,r,o.content,o.metadata);if(!f){n.error("No session key for ENCRYPTED session \u2014 dropping JSONL event (fail-closed, #638)",{type:o.type,sessionId:i});return}o.content=f.content,o.metadata=f.metadata,f.isEncrypted&&(o.isEncrypted=!0,n.debug("Event encrypted",{type:o.type}));let y=await this.appSyncClient.createEvent(o);n.debug("Event synced to backend",{type:o.type,encrypted:f.isEncrypted}),g&&m&&((0,d.isPersistedEventResult)(y)?await g.finalizeLegacyOwned([h]):g.clearLegacyInFlight(h))}catch(f){g&&m&&g.clearLegacyInFlight(h),n.error("Failed to sync event:",f)}}async handlePermissionRequestHook(e,t){if(!this.isExpectedSessionStateCurrent(t))return;let i=e.metadata||{},s=typeof i.tool_name=="string"?i.tool_name:"Tool",r=i.tool_input,o=this.stringifyToolInput(r),l={toolName:s,toolInput:r,rawInput:o,filePath:this.extractFilePathFromToolInput(s,r,o),diff:s==="apply_patch"?o:void 0,hint:this.buildPermissionRequestHint(s,r,o)},a=this.buildToolDetailsForInteractivePrompt(l),p=a.tool_name||this.mapToolNameForApproval(s),u=a.tool_input||this.buildFallbackToolInput(l),h=!!(p&&u),m=await this.tryParsePermissionRequestPromptFromTmux(Le=>this.parsedPromptMatchesApprovalContext(Le,l));if(!this.isExpectedSessionStateCurrent(t)){n.warn("[Hooks] Dropping stale PermissionRequest after session generation changed",{sessionId:t.sessionId,currentSessionId:this.sessionState?.sessionId});return}let g=m?.parsedPrompt??null;if(!g){n.warn("[Hooks] Suppressing PermissionRequest mobile prompt because exact Codex options are unavailable",{sessionId:t.sessionId,toolName:s,hint:l.hint}),this.raiseSuppressedBadge(t.sessionId,this.buildApprovalDedupeKey({toolName:s,toolInput:r,filePath:l.filePath,rawInput:o,hint:l.hint}));return}let f=this.buildApprovalDedupeKey({toolName:s,toolInput:r,filePath:l.filePath,rawInput:o,hint:l.hint});if(f&&this.hasRecentlyResolvedApprovalDedupeKey(t.sessionId,f)){n.info("[Hooks] Skipping PermissionRequest prompt; same approval was already answered recently",{sessionId:t.sessionId,toolName:s,dedupeKey:f});return}let y=this.buildCodexPromptPresentation(g);if(!y){n.warn("[Hooks] Suppressing PermissionRequest mobile prompt because Codex option hotkeys are unavailable",{sessionId:t.sessionId,toolName:s,hint:l.hint}),this.raiseSuppressedBadge(t.sessionId,f);return}let w=this.buildPermissionPromptId(i,s,r,o),T=this.buildApprovalPromptContent(y.content,{toolName:p,toolInput:u,hint:l.hint,filePath:l.filePath}),O={promptId:w,kind:y.kind,options:y.options,submitMap:y.submitMap,promptText:T,createdAt:Date.now(),source:"permission_hook",dedupeKey:f,requiresFollowUpText:y.requiresFollowUpText},se={isApprovalHint:!0,toolName:s,toolInput:r,hint:l.hint,filePath:l.filePath,diff:l.diff,rawInput:o,tool_name:p,tool_input:u,has_details:h,options:y.options,instructions:y.instructions,prompt_source:g?"permission_hook_tmux":"permission_hook",permission_mode:i.permission_mode,turn_id:i.turn_id,dedupe_key:f};n.info("[Hooks] Sending PermissionRequest interactive prompt",{sessionId:t.sessionId,toolName:s,promptId:w,promptSource:se.prompt_source,dedupeKey:f}),await this.enqueueOrEmitInteractivePrompt({prompt:O,content:T,metadata:se,ownerState:t})}async emitHooksCompatibilityModeNotice(e){if(this.hooksCompatibilityNoticeSent||!this.isExpectedSessionStateCurrent(e))return;this.hooksCompatibilityNoticeSent=!0;let t="Codex hooks are not active. CodeVibe is using compatibility mode: mobile approvals still mirror the desktop prompt, but timeline events may be delayed or incomplete. Open Codex /hooks and trust/enable CodeVibe hooks for full fidelity.",i={compatibility_mode:!0,reason:"codex_hooks_inactive",action:"trust_enable_codevibe_hooks"};n.warn("Codex hooks inactive; running in compatibility mode",{sessionId:e.sessionId});let s=this.encryptForEmit(t,i,d.EventType.NOTIFICATION,e.sessionId);if(s)try{await this.appSyncClient.createEvent({sessionId:e.sessionId,type:d.EventType.NOTIFICATION,source:d.EventSource.DESKTOP,content:s.content,metadata:s.metadata,timestamp:(0,d.prepareEventTimestamp)({orderingKey:e.sessionId}),...s.isEncrypted?{isEncrypted:!0}:{}})}catch(r){n.warn("Failed to emit hooks compatibility mode notification",{error:r})}}async handleApprovalPending(e){let t=this.sessionState;if(!t)return;let i=this.buildApprovalDedupeKey(e);if(i&&this.hasRecentlyResolvedApprovalDedupeKey(t.sessionId,i)){n.info("Skipping heuristic approval prompt; same approval was already answered recently",{callId:e.callId,toolName:e.toolName,dedupeKey:i});return}if(i&&this.hasActiveOrQueuedPermissionHookPrompt(i)){this.mergeCallIdIntoPromptByDedupeKey(i,e.callId),n.info("Skipping heuristic approval prompt; PermissionRequest hook already emitted it",{callId:e.callId,toolName:e.toolName,dedupeKey:i});return}n.info("Sending approval pending interactive prompt",e);try{let s=await this.tryParseInteractivePromptFromTmux(),r=s?.parsedPrompt??null,o=this.buildToolDetailsForInteractivePrompt(e,s?.snapshot),l=o.tool_name||this.mapToolNameForApproval(e.toolName),a=o.tool_input||this.buildFallbackToolInput(e),p=!!(l&&a);if(!r){n.warn("Suppressing heuristic approval prompt because exact Codex options are unavailable",{callId:e.callId,toolName:e.toolName,hint:e.hint}),this.raiseSuppressedBadge(t.sessionId,i);return}if(!this.parsedPromptMatchesApprovalContext(r,e)){n.warn("Suppressing heuristic approval prompt because parsed tmux prompt does not match the active tool",{callId:e.callId,toolName:e.toolName,hint:e.hint,parsedPromptText:r.promptText}),this.raiseSuppressedBadge(t.sessionId,i);return}let u=this.buildCodexPromptPresentation(r);if(!u){n.warn("Suppressing heuristic approval prompt because Codex option hotkeys are unavailable",{callId:e.callId,toolName:e.toolName,hint:e.hint}),this.raiseSuppressedBadge(t.sessionId,i);return}let h=u.options,m=this.buildApprovalPromptContent(u.content,{toolName:l,toolInput:a,hint:e.hint,filePath:e.filePath}),g={promptId:e.callId,callId:e.callId,kind:u.kind,options:h,submitMap:u.submitMap,promptText:u.promptText,createdAt:Date.now(),source:r?"tmux":"heuristic",dedupeKey:i,requiresFollowUpText:u.requiresFollowUpText},f={isApprovalHint:!0,toolName:e.toolName,toolInput:e.toolInput,hint:e.hint,callId:e.callId,filePath:e.filePath,diff:e.diff,rawInput:e.rawInput,tool_name:l,tool_input:a,has_details:p,options:h,instructions:u.instructions,prompt_source:r?"tmux":"heuristic",dedupe_key:i};n.debug("Interactive prompt (pre-encryption)",{sessionId:t.sessionId,callId:e.callId,contentPreview:m.substring(0,200),toolDetails:o,metadata:f}),await this.enqueueOrEmitInteractivePrompt({prompt:g,content:m,metadata:f,ownerState:t})}catch(s){n.error("Failed to send approval interactive prompt:",s)}}async handleTmuxPromptCandidate(e){let t=this.sessionState;if(!t){this.tmuxPaneObserver.resetLastPromptHash();return}let i=(0,C.parseInteractivePrompt)(e);if(!i){this.raiseSuppressedBadge(t.sessionId,null);return}let s=this.buildCodexPromptPresentation(i);if(!s){n.warn("Skipping tmux-detected prompt because Codex option hotkeys are unavailable",{parsedPromptText:i.promptText}),this.raiseSuppressedBadge(t.sessionId,null);return}let r=this.getMostRecentPendingToolCall();if(!r){if(await new Promise(O=>setTimeout(O,500)),!this.isExpectedSessionStateCurrent(t)){n.warn("Dropping tmux prompt candidate after session generation changed",{sessionId:t.sessionId,currentSessionId:this.sessionState?.sessionId});return}r=this.getMostRecentPendingToolCall()}let o=r?this.buildApprovalPromptContextFromPendingCall(r):null;if(o&&!this.parsedPromptMatchesApprovalContext(i,o)){n.warn("Skipping tmux-detected prompt because parsed prompt does not match pending tool call",{callId:r?.callId,toolName:r?.name,parsedPromptText:i.promptText}),this.raiseSuppressedBadge(t.sessionId,null);return}let l=o?null:this.buildApprovalPromptContextFromParsedPrompt(i),a=o||l;if(!a){n.warn("Skipping tmux-detected prompt because no tool context could be derived from the parsed prompt",{parsedPromptText:i.promptText}),this.raiseSuppressedBadge(t.sessionId,null);return}let p=a?this.buildApprovalDedupeKey(a):void 0;if(p&&this.hasRecentlyResolvedApprovalDedupeKey(t.sessionId,p)){n.info("Skipping tmux-detected prompt; same approval was already answered recently",{promptText:i.promptText,dedupeKey:p});return}if(p&&this.hasActiveOrQueuedPermissionHookPrompt(p)){r?.callId&&this.mergeCallIdIntoPromptByDedupeKey(p,r.callId),n.info("Skipping tmux-detected prompt; PermissionRequest hook already emitted it",{promptText:i.promptText,dedupeKey:p});return}let u=a?this.buildToolDetailsForInteractivePrompt(a,e):{},h=u.tool_name||this.mapToolNameForApproval(r?.name),m=u.tool_input||(a?this.buildFallbackToolInput(a):void 0),g=!!(h&&m),y={promptId:r?.callId||(0,Fe.v4)(),callId:r?.callId,kind:s.kind,options:s.options,submitMap:s.submitMap,promptText:s.promptText,createdAt:Date.now(),source:"tmux",dedupeKey:p,requiresFollowUpText:s.requiresFollowUpText},w={options:s.options,instructions:s.instructions,prompt_source:"tmux_live",tool_name:h,tool_input:m,has_details:g,dedupe_key:p},T=this.buildApprovalPromptContent(s.content,{toolName:h,toolInput:m,hint:a?.hint,filePath:a?.filePath});try{await this.enqueueOrEmitInteractivePrompt({prompt:y,content:T,metadata:w,ownerState:t})&&n.info("Sent tmux-detected interactive prompt",{sessionId:t.sessionId,promptText:i.promptText,kind:i.kind})}catch(O){n.error("Failed to send tmux-detected interactive prompt",{error:O})}}async enqueueOrEmitInteractivePrompt(e){if(!this.isExpectedSessionStateCurrent(e.ownerState))return n.warn("Dropping interactive prompt from stale session generation",{sessionId:e.ownerState.sessionId,currentSessionId:this.sessionState?.sessionId,source:e.prompt.source}),!1;if(e.prompt.dedupeKey&&this.hasRecentlyResolvedApprovalDedupeKey(e.ownerState.sessionId,e.prompt.dedupeKey))return n.debug("Skipping interactive prompt emission; same approval was already answered recently",{source:e.prompt.source,callId:e.prompt.callId,dedupeKey:e.prompt.dedupeKey}),!1;let t=this.pendingInteractivePrompt;if(!t){this.pendingInteractivePrompt=e.prompt;try{return await this.emitInteractivePromptEvent(e),!0}catch(i){throw this.pendingInteractivePrompt?.promptId===e.prompt.promptId&&(this.pendingInteractivePrompt=null),i}}return this.isSameInteractivePrompt(t,e.prompt)?(!t.callId&&e.prompt.callId&&(t.callId=e.prompt.callId,n.debug("Merged callId into existing interactive prompt",{source:t.source,callId:e.prompt.callId,promptText:t.promptText})),n.debug("Skipping duplicate interactive prompt emission",{existingSource:t.source,candidateSource:e.prompt.source,existingCallId:t.callId,candidateCallId:e.prompt.callId,promptText:e.prompt.promptText}),!1):this.queuedInteractivePrompts.some(i=>this.isSameInteractivePrompt(i.prompt,e.prompt))?(n.debug("Skipping duplicate queued interactive prompt",{candidateSource:e.prompt.source,candidateCallId:e.prompt.callId,promptText:e.prompt.promptText}),!1):(this.queuedInteractivePrompts.push(e),n.info("Queued interactive prompt behind active prompt",{activeCallId:t.callId,queuedCallId:e.prompt.callId,queueLength:this.queuedInteractivePrompts.length}),!1)}async emitInteractivePromptEvent(e){if(!this.isExpectedSessionStateCurrent(e.ownerState))return;let t=e.ownerState.sessionId,i=this.e1PromptRaises.get(e.prompt.promptId),s=i?i.record:this.newE1Raise(t,!0);e.prompt.promptId=s.promptId,e.prompt.dedupeKey&&this.e1ContentKeyIndex.set(`${t}::${e.prompt.dedupeKey}`,s.promptId),this.stashE1RaiseContent(s.promptId,e.content,e.metadata??{});let r=this.encryptForEmit(e.content,e.metadata,d.EventType.INTERACTIVE_PROMPT,t);r&&await this.appSyncClient.createEvent({sessionId:t,type:d.EventType.INTERACTIVE_PROMPT,source:d.EventSource.DESKTOP,content:r.content,metadata:r.metadata,...(0,d.raiseFieldsFromRecord)(s),timestamp:(0,d.prepareEventTimestamp)({orderingKey:t}),...r.isEncrypted?{isEncrypted:!0}:{}})}async emitNextQueuedInteractivePrompt(){if(this.pendingInteractivePrompt||this.queuedInteractivePrompts.length===0)return;let e=this.queuedInteractivePrompts.shift();if(e){if(!this.isExpectedSessionStateCurrent(e.ownerState)){n.warn("Discarding queued prompt from stale session generation",{sessionId:e.ownerState.sessionId,currentSessionId:this.sessionState?.sessionId}),await this.emitNextQueuedInteractivePrompt();return}this.pendingInteractivePrompt=e.prompt;try{await this.emitInteractivePromptEvent(e),n.info("Emitted next queued interactive prompt",{callId:e.prompt.callId,queueLength:this.queuedInteractivePrompts.length})}catch(t){this.pendingInteractivePrompt=null,n.error("Failed to emit queued interactive prompt",{error:t}),await this.emitNextQueuedInteractivePrompt()}}}removeQueuedInteractivePromptByCallId(e){let t=this.queuedInteractivePrompts.length,i=this.queuedInteractivePrompts.filter(s=>s.prompt.callId===e);for(let s of i)this.rememberResolvedApprovalDedupeKey(s.ownerState.sessionId,s.prompt.dedupeKey);this.queuedInteractivePrompts=this.queuedInteractivePrompts.filter(s=>s.prompt.callId!==e),this.queuedInteractivePrompts.length!==t&&n.debug("Removed resolved tool call from interactive prompt queue",{callId:e,removed:t-this.queuedInteractivePrompts.length})}async clearResolvedInteractivePrompt(e,t){let i=this.pendingInteractivePrompt;if(i&&(i.callId===e||t!==void 0&&i.dedupeKey===t)){this.rememberResolvedApprovalDedupeKey(this.sessionState?.sessionId,i.dedupeKey),this.pendingInteractivePrompt=null,await this.emitNextQueuedInteractivePrompt();return}this.removeQueuedInteractivePromptByCallId(e),t!==void 0&&this.removeQueuedInteractivePromptByDedupeKey(t)}removeQueuedInteractivePromptByDedupeKey(e){let t=this.queuedInteractivePrompts.length,i=this.queuedInteractivePrompts.filter(s=>s.prompt.dedupeKey===e);for(let s of i)this.rememberResolvedApprovalDedupeKey(s.ownerState.sessionId,s.prompt.dedupeKey);this.queuedInteractivePrompts=this.queuedInteractivePrompts.filter(s=>s.prompt.dedupeKey!==e),this.queuedInteractivePrompts.length!==t&&n.debug("Removed resolved deduped tool call from interactive prompt queue",{dedupeKey:e,removed:t-this.queuedInteractivePrompts.length})}mergeCallIdIntoPromptByDedupeKey(e,t){let i=this.pendingInteractivePrompt;if(i?.dedupeKey===e&&!i.callId){i.callId=t,n.debug("Merged callId into active deduped interactive prompt",{source:i.source,callId:t,dedupeKey:e});return}let s=this.queuedInteractivePrompts.find(r=>r.prompt.dedupeKey===e&&!r.prompt.callId);s&&(s.prompt.callId=t,n.debug("Merged callId into queued deduped interactive prompt",{source:s.prompt.source,callId:t,dedupeKey:e}))}isSameInteractivePrompt(e,t){return Date.now()-e.createdAt>vt?!1:e.callId&&t.callId?e.callId===t.callId:e.dedupeKey&&t.dedupeKey?e.dedupeKey===t.dedupeKey:e.kind===t.kind&&this.normalizePromptDedupeText(e.promptText)===this.normalizePromptDedupeText(t.promptText)}normalizePromptDedupeText(e){return e.replace(/\s+/g," ").trim().toLowerCase()}async startTmuxObserver(){let e=process.env.CODEVIBE_CODEX_TMUX_SESSION;if(!e)return n.debug("Skipping tmux pane observer start - no tmux session in environment"),!0;try{return await this.tmuxPaneObserver.start(e),!0}catch(t){return n.warn("Failed to start tmux pane observer",{tmuxSession:e,error:t}),!1}}async startTmuxObserverWithBeacon(e){try{await this.startTmuxObserver()?await v("daemon_init_step_completed",{step:"tmux_observer_start",path:e}):await v("daemon_init_step_failed",{step:"tmux_observer_start",path:e,error_class:"TmuxObserverStartFailed",error_message:"tmuxPaneObserver.start threw (see plugin log)"})}catch(t){await v("daemon_init_step_failed",{step:"tmux_observer_start",path:e,error_class:t?.name||"Error",error_message:t?.message||String(t)}),n.error("Failed to start tmux observer:",t)}}async tryParseInteractivePromptFromTmux(){try{let e=await this.tmuxPaneObserver.captureSnapshot(),t=(0,C.parseInteractivePrompt)(e);return n.debug("tmux prompt parse result",{parsed:!!t,kind:t?.kind,promptText:t?.promptText,snapshotPreview:this.summarizePromptSnapshot(e)}),{parsedPrompt:t,snapshot:e}}catch(e){return n.debug("tmux prompt parsing unavailable",{error:e}),null}}async tryParsePermissionRequestPromptFromTmux(e){let t=await this.tryParseInteractivePromptFromTmux();if(t?.parsedPrompt&&(!e||e(t.parsedPrompt,t.snapshot)))return t;t?.parsedPrompt&&n.warn("Ignoring parsed PermissionRequest prompt because it does not match the active tool",{promptText:t.parsedPrompt.promptText}),await new Promise(s=>setTimeout(s,250));let i=await this.tryParseInteractivePromptFromTmux();return i?.parsedPrompt&&(!e||e(i.parsedPrompt,i.snapshot))?i:(i?.parsedPrompt&&n.warn("Ignoring retried PermissionRequest prompt because it does not match the active tool",{promptText:i.parsedPrompt.promptText}),i?.snapshot||t?.snapshot?{parsedPrompt:null,snapshot:i?.snapshot||t?.snapshot||""}:null)}buildPromptPresentation(e){return e?{content:e.promptText,promptText:e.promptText,kind:e.kind,options:e.options,submitMap:e.submitMap,instructions:this.buildPromptInstructions(e),requiresFollowUpText:e.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}}buildCodexPromptPresentation(e){let t=this.buildPromptPresentation(e),i=this.buildSubmitMapFromCodexOptionHotkeys(t.options);return i?{...t,submitMap:i,instructions:this.buildCodexPermissionInstructions(t.options),requiresFollowUpText:this.optionsRequireFollowUpText(t.options)}:null}buildSubmitMapFromCodexOptionHotkeys(e){let t={};for(let i of e){let s=i.text.match(/\(([^)]+)\)\s*$/)?.[1]?.trim().toLowerCase();if(!s||!bt.test(s))return null;s==="esc"||s==="escape"?t[i.number]=F:t[i.number]=s}return t}buildCodexPermissionInstructions(e){let t=e.find(o=>/\((?:y|yes)\)\s*$/i.test(o.text)),i=e.find(o=>/\(p\)\s*$/i.test(o.text)),s=e.find(o=>/\((?:esc|escape)\)\s*$/i.test(o.text)),r=[];return t&&r.push(`${t.number} to approve`),i&&r.push(`${i.number} to persist the desktop allow rule`),s&&r.push(`${s.number} followed by what to change`),r.length>0?`Reply with ${r.join(", ")}`:"Reply with the number of the option you want"}optionsRequireFollowUpText(e){return e.some(t=>/what to (?:do differently|change)|instructions/i.test(t.text))}getMostRecentPendingToolCall(){let e=this.approvalDetector.getPendingCalls();return e.length===0?null:e.reduce((t,i)=>i.timestamp>t.timestamp?i:t)}hasActiveOrQueuedPermissionHookPrompt(e){let t=this.pendingInteractivePrompt;return t?.source==="permission_hook"&&t.dedupeKey===e?!0:this.queuedInteractivePrompts.some(i=>i.prompt.source==="permission_hook"&&i.prompt.dedupeKey===e)}hasRecentlyResolvedApprovalDedupeKey(e,t){return e?(this.pruneResolvedApprovalDedupeKeys(),this.resolvedApprovalDedupeKeys.has(`${e}::${t}`)):!1}rememberResolvedApprovalDedupeKey(e,t){!e||!t||(this.pruneResolvedApprovalDedupeKeys(),this.resolvedApprovalDedupeKeys.set(`${e}::${t}`,Date.now()))}pruneResolvedApprovalDedupeKeys(){let e=Date.now()-St;for(let[t,i]of this.resolvedApprovalDedupeKeys.entries())i<e&&this.resolvedApprovalDedupeKeys.delete(t)}buildApprovalDedupeKey(e){let t=e.toolName||"Tool",i=this.firstString(e.toolInput?.command,e.toolInput?.cmd,e.rawInput);if(i)return`command:${this.hashDedupeValue(`${this.mapToolNameForApproval(t)||t}:${i.trim()}`)}`;let s=this.firstString(e.filePath,e.toolInput?.file_path,e.toolInput?.path,e.toolInput?.filePath);if(s)return`file:${this.hashDedupeValue(`${t}:${s}`)}`;let r=this.firstString(e.hint);if(r)return`hint:${this.hashDedupeValue(`${t}:${r}`)}`}hashDedupeValue(e){return ee.createHash("sha256").update(e.replace(/\s+/g," ").trim().toLowerCase()).digest("hex").slice(0,16)}buildPermissionPromptId(e,t,i,s){let r=typeof e.turn_id=="string"&&e.turn_id.trim().length>0?e.turn_id.trim():void 0,o=ee.createHash("sha256").update(`${t}:${s||this.stringifyToolInput(i)}`).digest("hex").slice(0,12);return`permission-${r||"turn"}-${o}`}buildPermissionRequestHint(e,t,i){let s=this.firstString(t?.command,t?.cmd);if(s)return`Command: ${this.truncateApprovalDetail(s,80)}`;let r=this.extractFilePathFromToolInput(e,t,i);return r?`File: ${r}`:`Tool: ${this.mapToolNameForApproval(e)||e}`}extractFilePathFromToolInput(e,t,i){let s=this.firstString(t?.file_path,t?.path,t?.filePath);if(s)return s;if(e==="apply_patch"&&i){let r=i.match(/\*\*\* (?:Update|Add|Delete) File: (.+)/);if(r)return r[1].trim()}}stringifyToolInput(e){if(e!=null){if(typeof e=="string")return e;try{return JSON.stringify(e)}catch{return String(e)}}}buildApprovalPromptContextFromPendingCall(e){return{toolName:e.name,filePath:e.filePath,diff:e.diff,toolInput:e.parsedInput,rawInput:e.input,hint:e.filePath?`File: ${e.filePath}`:`Tool: ${this.mapToolNameForApproval(e.name)||e.name}`}}buildApprovalPromptContextFromParsedPrompt(e){let t=this.extractShellCommandFromPromptText(e.promptText);return t?{toolName:"Bash",toolInput:{command:t},rawInput:t,hint:`Command: ${this.truncateApprovalDetail(t,80)}`}:null}parsedPromptMatchesApprovalContext(e,t){let i=this.firstString(t.toolInput?.command,t.toolInput?.cmd);if(i){let r=ie(e.promptText);if(!r||r.length===0)return!1;let o=this.normalizeApprovalCommand(i);return this.normalizeApprovalCommand(r.join(" "))===o||r.length>1&&this.normalizeApprovalCommand(r.join(""))===o}let s=this.firstString(t.filePath,t.toolInput?.file_path,t.toolInput?.path,t.toolInput?.filePath);return s?e.promptText.includes(s):!1}normalizeApprovalCommand(e){return e.replace(/\s+/g," ").trim()}extractShellCommandFromPromptText(e){return Me(e)}buildPromptInstructions(e){return e.kind==="yes_no"&&e.requiresFollowUpText?"Reply with 1 to approve, or 2 followed by what to change":e.kind==="yes_no"?"Reply with 1 for yes or 2 for no":e.kind==="numbered"?"Reply with the number of the option you want":"Reply with your response"}buildApprovalPromptContent(e,t){let i=e.trim(),s=i==="Codex is waiting for approval.",r=/^\$\s+/.test(i),o=t.toolName?`${t.toolName} requires approval.`:"Codex is waiting for approval.",l=[s||r||!i?o:i],a=typeof t.toolInput?.command=="string"?t.toolInput.command.trim():void 0;if(a&&!l.join(`
14
- `).includes(a))return l.push("Command:",this.truncateApprovalDetail(a,240)),l.join(`
15
- `);let p=t.filePath||t.toolInput?.file_path;return typeof p=="string"&&p.length>0&&!l.join(`
16
- `).includes(p)?(l.push(`File: ${p}`),l.join(`
17
- `)):(t.hint&&!l.join(`
18
- `).includes(t.hint)&&l.push(t.hint),l.join(`
19
- `))}truncateApprovalDetail(e,t){return e.length>t?`${e.slice(0,t-3)}...`:e}summarizePromptSnapshot(e){return e.split(`
20
- `).map(t=>t.trimEnd()).filter(t=>t.length>0).slice(-12).map(t=>t.slice(0,160)).join(`
21
- `)}translatePromptResponse(e){let t=this.pendingInteractivePrompt;if(!t)return{primaryInput:e};let s=e.trim().match(/^(\d+)(?:[,.:;\-\s]+([\s\S]+))?$/);if(!s)return{primaryInput:e};let r=s[1],o=s[2]?.trim(),l=t.submitMap[r];return l?t.requiresFollowUpText&&o?{primaryInput:l,followUpInput:o}:{primaryInput:l}:{primaryInput:e}}getEventPromptId(e){let t=e.promptId;return typeof t=="string"&&t.trim().length>0?t.trim():null}isApprovalResponseLike(e){let t=e.trim().toLowerCase();return/^(?:\d+|y|yes|n|no)(?:[\s,.:;-].*)?$/.test(t)}async emitRejectedPromptResponseNotification(e,t,i){if(!this.isExpectedSessionStateCurrent(i))return;let s=i.sessionId,r="Response ignored because it was not tied to the current prompt. Please reply to the latest prompt again.",o={prompt_response_rejected:!0,reason:e,eventId:t.eventId,eventPromptId:this.getEventPromptId(t),activePromptId:this.pendingInteractivePrompt?.promptId},l=this.encryptForEmit(r,o,d.EventType.NOTIFICATION,s);if(l)try{await this.appSyncClient.createEvent({sessionId:s,type:d.EventType.NOTIFICATION,source:d.EventSource.DESKTOP,content:l.content,metadata:l.metadata,timestamp:(0,d.prepareEventTimestamp)({orderingKey:s}),...l.isEncrypted?{isEncrypted:!0}:{}})}catch(a){n.warn("Failed to emit rejected prompt response notification",{reason:e,error:a})}}buildToolDetailsForInteractivePrompt(e,t){let i=e.toolName,s=e.toolInput&&typeof e.toolInput=="object"?e.toolInput:void 0;if(i==="apply_patch"){let o=e.diff||e.rawInput;if(o){let{oldString:l,newString:a,oldStartLine:p,newStartLine:u}=this.extractOldNewFromPatch(o),h=t?this.extractDiffLineAnchorsFromSnapshot(t):{};return{tool_name:"Edit",tool_input:{file_path:e.filePath,content:o,diff:e.diff,raw_patch:e.rawInput,old_string:l,new_string:a,old_start_line:p??h.oldStartLine,new_start_line:u??h.newStartLine}}}}if(i==="exec_command"||i==="shell_command"||i==="shell"||i==="Bash"){let o=this.firstString(s?.command,s?.cmd,e.rawInput,e.hint);if(o)return{tool_name:"Bash",tool_input:{command:o,output:s?.output}}}let r={};return e.filePath&&(r.file_path=e.filePath),e.diff&&(r.diff=e.diff),e.rawInput&&(r.raw_input=e.rawInput),Object.keys(r).length>0?{tool_name:i||"Tool",tool_input:r}:{}}firstString(...e){for(let t of e)if(typeof t=="string"&&t.trim().length>0)return t}buildFallbackToolInput(e){let t={};return e.filePath&&(t.file_path=e.filePath),e.diff&&(t.diff=e.diff),e.rawInput&&(t.raw_input=e.rawInput),e.toolInput&&typeof e.toolInput=="object"&&(t.parsed_input=e.toolInput),Object.keys(t).length>0?t:void 0}mapToolNameForApproval(e){return e?{exec_command:"Bash",Bash:"Bash",apply_patch:"Edit",shell_command:"Bash",shell:"Bash",Edit:"Edit",Write:"Write"}[e]||e:void 0}extractOldNewFromPatch(e){let t=[],i=[],s=/^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/,r=0,o=0,l=0,a,p;for(let u of e.split(`
22
- `)){let h=u.match(s);if(h){r+=1,o=Number.parseInt(h[1],10),l=Number.parseInt(h[2],10);continue}if(!(u.startsWith("***")||u.startsWith("---")||u.startsWith("+++")||u.startsWith("*** End Patch"))){if(u.startsWith("-"))a===void 0&&(a=o),t.push(u.slice(1)),o+=1;else if(u.startsWith("+"))p===void 0&&(p=l),i.push(u.slice(1)),l+=1;else if(u.startsWith(" ")){let m=u.slice(1);t.push(m),i.push(m),o+=1,l+=1}}}return{oldString:t.join(`
18
+ `).trim();return(0,q.createHash)("sha256").update(i).digest("hex")}};var M=require("@quantiya/codevibe-core");var fe=E(require("express")),O=E(require("fs")),ze=E(require("path")),He=E(require("os"));var kt=1800*1e3,At=60*1e3,Mt=1440*60*1e3;var Z=class{constructor(){this.assignedPort=0;this.portRefreshTimer=null;this.app=(0,fe.default)(),this.setupMiddleware(),this.setupRoutes(),this.tmuxSession=process.env.CODEVIBE_CODEX_TMUX_SESSION}getPort(){return this.assignedPort}setupMiddleware(){this.app.use(fe.default.json({limit:"1mb"})),this.app.use((t,e,i)=>{n.debug(`${t.method} ${t.path}`,{body:t.body}),i()})}setupRoutes(){this.app.get("/health",(t,e)=>{e.json({success:!0,service:"codevibe-codex",pid:process.pid,data:{status:"healthy",uptime:process.uptime()}})}),this.app.post("/event",this.handleEvent.bind(this))}async handleEvent(t,e){try{let i=t.body;if(!i.session_id||!i.hook_event_name){e.status(400).json({success:!1,error:"Missing session_id or hook_event_name"});return}let s=this.transformHookToEvent(i);n.info("Received hook event",{sessionId:i.session_id,hookEvent:i.hook_event_name,type:s.type}),this.eventHandler&&await this.eventHandler(s),e.json({success:!0})}catch(i){n.error("Error handling event:",i),e.status(500).json({success:!1,error:i instanceof Error?i.message:"Unknown error"})}}transformHookToEvent(t){let e={cwd:t.cwd,hook_event_name:t.hook_event_name,transcript_path:t.transcript_path,...t.metadata||{}},i,s;switch(t.hook_event_name){case"SessionStart":i="NOTIFICATION",s="Session started",e.source=t.source,e.model=t.model;break;case"UserPromptSubmit":i="USER_PROMPT",s=t.prompt||"";break;case"PreToolUse":i="NOTIFICATION",s="PreToolUse observed",e.tool_name=t.tool_name,e.tool_input=t.tool_input,e.tool_use_id=t.tool_use_id,e.approval_status="observed_pre_tool",e.requires_user_action=!1;break;case"PermissionRequest":i="NOTIFICATION",s="PermissionRequest observed",e.tool_name=t.tool_name,e.tool_input=t.tool_input,e.permission_mode=t.permission_mode,e.turn_id=t.turn_id,e.requires_user_action=!0;break;case"PostToolUse":i="TOOL_USE",s=JSON.stringify({tool_name:t.tool_name,tool_input:t.tool_input,tool_response:t.tool_response}),e.tool_name=t.tool_name,e.tool_input=t.tool_input,e.tool_use_id=t.tool_use_id;break;case"Stop":i="ASSISTANT_RESPONSE",s=t.last_assistant_message||"";break;default:i="NOTIFICATION",s=`Hook: ${t.hook_event_name}`}return{session_id:t.session_id,hook_event_name:t.hook_event_name,type:i,source:"DESKTOP",content:s,metadata:e}}onEvent(t){this.eventHandler=t}async start(){return new Promise((t,e)=>{try{this.server=this.app.listen(0,"localhost",()=>{let i=this.server.address();this.assignedPort=i.port,n.info(`HTTP API listening on http://localhost:${this.assignedPort}`),this.writePortFile(this.assignedPort),this.startPortFileKeepalive(),t(this.assignedPort)}),this.server.on("error",i=>{n.error("HTTP server error:",i),e(i)})}catch(i){e(i)}})}portFilePath(){return this.tmuxSession?ze.join(He.tmpdir(),`codevibe-codex-${this.tmuxSession}.port`):null}writePortFile(t){let e=this.portFilePath();if(!e){n.warn("No CODEVIBE_CODEX_TMUX_SESSION set, skipping port file");return}try{O.writeFileSync(e,t.toString()),n.info(`Port file written: ${e} -> ${t}`)}catch(i){n.error(`Failed to write port file: ${e}`,i)}}removePortFile(){let t=this.portFilePath();if(t)try{O.existsSync(t)&&(O.unlinkSync(t),n.info(`Port file removed: ${t}`))}catch(e){n.warn(`Failed to remove port file: ${t}`,e)}}startPortFileKeepalive(){this.portRefreshTimer&&(clearInterval(this.portRefreshTimer),this.portRefreshTimer=null);let t=Number(process.env.CODEVIBE_PORTFILE_REFRESH_MS),e=Number.isFinite(t)&&t>0?Math.min(Mt,Math.max(At,t)):kt;this.portRefreshTimer=setInterval(()=>this.refreshPortFile(),e)}refreshPortFile(){let t=this.portFilePath();if(t)try{let e=new Date;O.utimesSync(t,e,e)}catch(e){e?.code==="ENOENT"?this.writePortFile(this.assignedPort):n.warn(`Port-file refresh failed: ${t}`,e)}}async stop(){return this.portRefreshTimer&&(clearInterval(this.portRefreshTimer),this.portRefreshTimer=null),this.removePortFile(),new Promise(t=>{this.server?this.server.close(()=>{n.info("HTTP API stopped"),t()}):t()})}};var ee=class{constructor(t={}){this.pendingBySession=new Map;this.expiryMs=t.expiryMs??1e4,this.minFuzzyEchoLength=t.minFuzzyEchoLength??16,this.minFuzzyEchoRatio=t.minFuzzyEchoRatio??.35,this.now=t.now??Date.now}track(t,e){let i=this.normalize(e);if(!i)return;let s=this.validEntries(t);s.push({normalized:i,timestamp:this.now()}),this.pendingBySession.set(t,s)}forget(t,e){let i=this.normalize(e);if(!i)return;let s=!1,r=this.validEntries(t).filter(o=>!s&&o.normalized===i?(s=!0,!1):!0);this.replaceEntries(t,r)}consumeIfDuplicate(t,e){let i=this.normalize(e);if(!i)return null;let s=null,r=this.validEntries(t).filter(o=>{if(!s){let a=this.matchType(o.normalized,i);if(a)return s={matchType:a,originalLength:o.normalized.length,echoLength:i.length},!1}return!0});return this.replaceEntries(t,r),s}validEntries(t){let e=this.now()-this.expiryMs;return(this.pendingBySession.get(t)||[]).filter(i=>i.timestamp>=e)}replaceEntries(t,e){e.length>0?this.pendingBySession.set(t,e):this.pendingBySession.delete(t)}matchType(t,e){if(t===e)return"exact";let i=e.length>=this.minFuzzyEchoLength,s=e.length/t.length>=this.minFuzzyEchoRatio;return i&&s&&t.endsWith(e)?"suffix":null}normalize(t){return t.replace(/\s+/g," ").trim()}};var je=E(require("crypto")),Ge=E(require("fs")),Ve=E(require("https")),te=E(require("os")),Ye=E(require("path")),Nt="G-GS74YEQTB8",Dt="lAfOF6OxRzSQ-NsLBRjhAg",Bt="www.google-analytics.com",Lt=`/mp/collect?measurement_id=${Nt}&api_secret=${Dt}`,We=800;function Ut(){try{let p=Ye.resolve(__dirname,"..","package.json"),t=Ge.readFileSync(p,"utf-8"),e=JSON.parse(t);if(typeof e.version=="string"&&e.version.length>0&&e.version.length<30)return e.version}catch{}return"unknown"}var Kt=Ut();function qt(){let p=typeof process.getuid=="function"?process.getuid():0;return je.createHash("sha256").update(`${te.hostname()}-${p}`).digest("hex").substring(0,36)}function $t(p){if(!p)return"";let t=te.homedir(),e=p.replace(/[\n\r\t]/g," ");if(t&&t.length>0){let i=t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");e=e.replace(new RegExp(i,"g"),"~")}return e=e.replace(/\/Users\/[^/\s"'`]+/g,"/Users/<user>").replace(/\/home\/[^/\s"'`]+/g,"/home/<user>").replace(/[^\x20-\x7E]/g,""),e.trim().substring(0,100)}function P(p,t){return new Promise(e=>{let i,s=!1,r=()=>{s||(s=!0,clearTimeout(o),e())},o=setTimeout(()=>{try{i?.destroy()}catch{}r()},We);typeof o.unref=="function"&&o.unref();try{let a={...t};typeof a.error_message=="string"&&(a.error_message=$t(a.error_message));let l=JSON.stringify({client_id:qt(),events:[{name:p,params:{agent:"codex",plugin_version:Kt,platform:process.platform,source:process.env.CODEVIBE_TELEMETRY_SOURCE||"production",...a}}]});i=Ve.request({hostname:Bt,path:Lt,method:"POST",headers:{"Content-Type":"application/json"},timeout:We},c=>{c.resume(),c.on("end",r),c.on("close",r),c.on("error",r)}),i.on("error",r),i.on("timeout",()=>{try{i?.destroy()}catch{}r()}),i.on("close",r),i.write(l),i.end()}catch{r()}})}var ie=class{constructor(){this.originated=new Set;this.externallyResolved=new Set}markOriginated(t){this.originated.add(t)}clearOriginated(t){this.originated.delete(t)}getOriginated(){return new Set(this.originated)}getExternallyResolved(){return new Set(this.externallyResolved)}isExternallyResolved(t){return this.externallyResolved.has(t)}dispatch(t){let{gateId:e}=t;return this.originated.has(e)?(this.originated.delete(e),this.externallyResolved.add(e),"self_echo"):(this.externallyResolved.add(e),"foreign")}clear(){this.originated.clear(),this.externallyResolved.clear()}};function Xe(p){return`[CodeVibe] Decision recorded on another device: ${p} \u2014 type any number to dismiss the open AskUserQuestion`}var Ze=(0,ve.promisify)(re.exec),zt=(0,ve.promisify)(re.execFile),Ht="/quit",$="CODEVIBE_CODEX_TMUX_SESSION",ge="CODEVIBE_CODEX_DAEMON_RECOVERY",Wt=1e3,jt=600*1e3,Gt=30*1e3,Vt=/^(?:[a-z0-9]|esc|escape)$/;async function Yt(p,t){let e=async(i,s)=>{try{await Ze(i)}catch(r){n.warn("tmux send-keys failed during self-terminate",{sessionName:p,label:s,error:String(r)})}};await e(`tmux send-keys -t "${p}" C-c`,"ctrl-c"),await new Promise(i=>setTimeout(i,200)),await e(`tmux send-keys -t "${p}" -l "${t}"`,"quit-text"),await new Promise(i=>setTimeout(i,500)),await e(`tmux send-keys -t "${p}" Enter`,"enter")}function Se(p){let t=p.split(`
19
+ `),e=o=>/^[\s>]*\d+\.\s/.test(o),i=-1;for(let o=t.length-1;o>=0;o-=1)if(/^\s*\$\s+/.test(t[o])){i=o;break}if(i===-1)return;let s=t[i].trim().match(/^\$\s+(.+)$/);if(!s?.[1]?.trim())return;let r=[s[1].trim()];for(let o=i+1;o<t.length;o+=1){let a=t[o];if(e(a)||/^\s*$/.test(a)||/^\s*\$\s+/.test(a))break;r.push(a.trim())}return r.length>0?r:void 0}function et(p){let t=Se(p);if(!t)return;let e=t.join(" ").trim();return e.length>0?e:void 0}var se=class p{constructor(){this.sessionState=null;this.unsubscribe=null;this.sessionKey=null;this.sessionIsEncrypted=!1;this.sessionKeyGen=null;this.e1PromptRaises=new Map;this.e1NeedsReRaise=new Set;this.e1RetirementOrphans=new Map;this.e1ContentKeyIndex=new Map;this.e1TtlRefreshTimer=null;this.toolActivity=null;this.toolActivityStartPromise=null;this.toolActivityLegacyRecorders=new Map;this.pendingInteractivePrompt=null;this.queuedInteractivePrompts=[];this.isInitializingSession=!1;this.bufferedLogEntries=[];this.logEntryChain=Promise.resolve();this.hookEventChain=Promise.resolve();this.hooksActive=!1;this.hooksCompatibilityNoticeSent=!1;this.resolvedApprovalDedupeKeys=new Map;this.subscribedSessionId=null;this.subscribedSessionState=null;this.terminalInputChain=Promise.resolve();this.terminalInputBlockedStates=new WeakSet;this.mobilePromptDeduper=new ee({expiryMs:1e4});this.launchSessionInitPromise=null;this.sessionStartedInitPromise=null;this.bootstrapSessionId=null;this.recoveryBootstrapOwnerBound=!1;this.resumeBackendSession=u.resumeOrCreateSession;this.sessionBootstrapFailureCount=0;this.sessionBootstrapRetryAt=0;this.retiredSessionRecoveryPromise=null;this.lifecycleGeneration=0;this.isStopping=!1;this.tmuxLifecycleTimer=null;this.tmuxLifecycleCheckInFlight=!1;this.tmuxLifecycleObservedAlive=!1;this.v1Bridge=new ie;this.orchestrationClient=null;this.userDecisionUnsubscribe=null;this.httpApi=new Z,this.sessionWatcher=new j,this.approvalDetector=new Y,this.promptResponder=new X,this.tmuxPaneObserver=new J}static{this.E1_TTL_REFRESH_MS=300*1e3}async start(){n.info("Starting CodeVibe Codex companion server",{environment:(0,u.getEnvironment)()}),this.appSyncClient=new u.AppSyncClient,await this.appSyncClient.authenticateWithStoredTokens()||(n.error('Authentication failed. Run "codevibe login" first.'),console.error('Not authenticated. Run "codevibe login" to sign in.'),process.exit(1)),n.info("Authenticated successfully",{userId:this.appSyncClient.getCurrentUserId(),email:this.appSyncClient.getCurrentUserEmail()}),await(0,u.registerDeviceEncryptionKey)(this.appSyncClient,n),(0,u.startDeviceKeyWatcher)(this.appSyncClient,n),(0,u.pushDetectedAgents)(this.appSyncClient,n).catch(i=>{n.warn("Failed to update available agents (non-fatal)",{error:i?.message})});try{let i=await this.appSyncClient.sweepOrphanSessions({agentType:"CODEX"});i>0&&n.info("Orphan sweep: marked stale Codex sessions INACTIVE",{swept:i})}catch(i){n.warn("Orphan sweep failed, continuing startup",{error:i instanceof Error?i.message:String(i)})}this.httpApi.onEvent(this.handleEventFromHook.bind(this));let e=await this.httpApi.start();n.info("HTTP API started for hooks",{port:e}),this.startTmuxLifecycleMonitor(),await this.createLaunchSession(),this.setupEventHandlers(),this.sessionWatcher.start(),n.info("CodeVibe Codex companion server started")}async createLaunchSession(){if(this.isStopping)return;if(process.env[ge]==="1"){n.info("Recovery daemon deferring backend bootstrap until the native rollout is known");return}let t=this.lifecycleGeneration,e=process.env.CODEVIBE_CODEX_TMUX_SESSION;if(!e){n.warn("No CODEVIBE_CODEX_TMUX_SESSION \u2014 skipping launch session");return}let i;this.launchSessionInitPromise=new Promise(s=>{i=s});try{let s=process.env.CODEX_WORKING_DIRECTORY||process.cwd(),r=this.getOrCreateBootstrapSessionId(),o=this.appSyncClient.getCurrentUserId();n.info("Creating launch session",{sessionId:r,projectPath:s});let a=!1;try{let l=await(0,u.resumeOrCreateSession)({sessionId:r,userId:o,agentType:u.AgentType.CODEX,projectPath:s,metadata:{launchSession:!0}},this.appSyncClient,n);if(r=l.sessionId,!this.isLifecycleCurrent(t)){await this.retireCancelledBootstrap(r,"launch_session");return}this.sessionKey=l.sessionKey,this.sessionKeyGen=l.sessionKeyGen,this.sessionIsEncrypted=!!l.sessionKey,a=!0,this.sessionState={sessionId:r,userId:o,projectPath:s,cwd:s,createdAt:new Date,subscriptionActive:!1,metadata:{launchSession:!0},encryptionSnapshot:{sessionKey:l.sessionKey,sessionIsEncrypted:!!l.sessionKey},codexSessionId:e,codexLogFile:void 0},this.clearSessionBootstrapFailure(),this.bootstrapSessionId=null,await P("daemon_init_step_completed",{step:"session_resume_or_create",path:"launch_session"})}catch(l){if(!this.isLifecycleCurrent(t)){let c=this.authoritativeSessionIdFromError(l)??r;await this.retireCancelledBootstrap(c,"launch_session_error");return}await P("daemon_init_step_failed",{step:"session_resume_or_create",path:"launch_session",error_class:l?.name||"Error",error_message:l?.message||String(l)}),l?.code==="ENCRYPTED_SESSION_NO_KEY"&&(this.sessionIsEncrypted=!0,this.sessionKey=null),this.bindBootstrapIdentityFromError(l),this.recordSessionBootstrapFailure(l,"launch_session"),n.error("Failed to create/resume launch session (non-fatal)",{error:l})}if(!a)return;await P("daemon_init_step_completed",{step:"session_state_set",path:"launch_session"});try{this.subscribeToMobileEvents(r)?await P("daemon_init_step_completed",{step:"subscribe_mobile_events",path:"launch_session"}):await P("daemon_init_step_failed",{step:"subscribe_mobile_events",path:"launch_session",error_class:"SubscriptionSetupFailed",error_message:"subscribeToMobileEvents returned false"})}catch(l){await P("daemon_init_step_failed",{step:"subscribe_mobile_events",path:"launch_session",error_class:l?.name||"Error",error_message:l?.message||String(l)}),n.error("Failed to subscribe to mobile events for launch session",{error:l})}try{await this.appSyncClient.startHeartbeat(r)?await P("daemon_init_step_completed",{step:"heartbeat_start",path:"launch_session"}):(await P("daemon_init_step_failed",{step:"heartbeat_start",path:"launch_session",error_class:"InitialHeartbeatNotPersisted",error_message:"Backend did not confirm the initial heartbeat mutation"}),n.error("Initial launch-session heartbeat was not persisted",{sessionId:r}))}catch(l){await P("daemon_init_step_failed",{step:"heartbeat_start",path:"launch_session",error_class:l?.name||"Error",error_message:l?.message||String(l)}),n.error("Failed to start heartbeat for launch session",{error:l})}try{await this.subscribeToOnApplyUserDecision(r)}catch(l){n.error("Failed to subscribe to onApplyUserDecision for launch session (non-fatal)",{error:l})}this.startMobileEndWatcher(r),n.info("Launch session created",{sessionId:r})}finally{i()}}encryptForEmit(t,e,i,s){let r=this.sessionState;if(!r||r.sessionId!==s||!r.encryptionSnapshot)return n.warn("Dropping event from stale or unresolved session generation (#638)",{type:i,sessionId:s,currentSessionId:r?.sessionId}),null;let{sessionKey:o,sessionIsEncrypted:a}=r.encryptionSnapshot,l=(0,u.encryptForEmit)(o,a,t,e);return l===null&&n.error("No session key for ENCRYPTED session \u2014 dropping event (fail-closed, #638)",{type:i,sessionId:s}),l}newE1Raise(t,e,i){let{record:s}=(0,u.newPromptRaise)({sessionId:t,producerKind:"PLUGIN_APPROVAL",mobileActionable:e,agentType:"CODEX",...i!==void 0&&{title:i}});return this.e1PromptRaises.set(s.promptId,{record:s}),this.e1TtlRefreshTimer||this.startE1TtlRefresh(),s}stashE1RaiseContent(t,e,i){let s=this.e1PromptRaises.get(t);s&&(s.contentPlain=e,s.metadataPlain=i)}async emitSuppressedPromptCarrier(t,e){let i="\u26A0\uFE0F A prompt is waiting in your desktop terminal, but its options could not be shown here. Please answer it on your desktop.",s=this.encryptForEmit(i,{e1SuppressedPrompt:!0},u.EventType.NOTIFICATION,t);return s?(await this.appSyncClient.createEvent({sessionId:t,type:u.EventType.NOTIFICATION,source:u.EventSource.DESKTOP,content:s.content,metadata:s.metadata,...(0,u.raiseFieldsFromRecord)(e),notificationText:i,timestamp:(0,u.prepareEventTimestamp)({orderingKey:t}),isEncrypted:s.isEncrypted?!0:void 0}),n.info("E1: emitted suppressed-prompt badge carrier (mobileActionable=false)",{sessionId:t,promptId:e.promptId}),!0):!1}raiseSuppressedBadge(t,e){if(e){let s=`${t}::${e}`,r=this.e1ContentKeyIndex.get(s);if(r&&this.e1PromptRaises.has(r))return;r&&this.e1ContentKeyIndex.delete(s)}let i=this.newE1Raise(t,!1);e&&this.e1ContentKeyIndex.set(`${t}::${e}`,i.promptId),this.emitSuppressedPromptCarrier(t,i).catch(s=>{n.error("E1: failed to emit suppressed-prompt badge carrier",{sessionId:t,promptId:i.promptId,error:s instanceof Error?s.message:String(s)})})}snapshotOpenE1(t){return[...this.e1PromptRaises.values()].filter(e=>e.record.sessionId===t).map(e=>({record:{...e.record},...e.contentPlain!==void 0&&{contentPlain:e.contentPlain},...e.metadataPlain!==void 0&&{metadataPlain:e.metadataPlain}}))}parkRetirementOrphans(t){for(let e of this.snapshotOpenE1(t))this.e1RetirementOrphans.set(e.record.promptId,e)}async drainRetirementOrphans(t){if(this.e1RetirementOrphans.size!==0){n.info("E1 (\xA76a-4): re-homing parked retirement-orphan prompts onto replacement session",{replacementSessionId:t,count:this.e1RetirementOrphans.size});for(let[e,i]of[...this.e1RetirementOrphans]){let r=this.e1PromptRaises.get(e)??{record:{...i.record},...i.contentPlain!==void 0&&{contentPlain:i.contentPlain},...i.metadataPlain!==void 0&&{metadataPlain:i.metadataPlain}};if(r.record.sessionId=t,this.e1PromptRaises.set(e,r),!await this.reRaiseOneE1(t,r,{untilAck:!0}))break;this.e1RetirementOrphans.delete(e)}this.e1TtlRefreshTimer||this.startE1TtlRefresh()}}isReRaiseTargetCurrent(t){return!this.isStopping&&this.sessionState!==null&&this.sessionState.sessionId===t}async reRaiseOneE1(t,e,i){let s=e.record,r=s.mobileActionable&&e.contentPlain!==void 0,o=i?.untilAck??!1,a=4,l=4e3;for(let c=1;o||c<=a;c++){if(o&&!this.isReRaiseTargetCurrent(t))return this.e1NeedsReRaise.add(s.promptId),n.warn("E1: retirement re-raise aborted (stopping/superseded) \u2014 parked",{sessionId:t,promptId:s.promptId}),!1;try{if(r){let d=this.encryptForEmit(e.contentPlain,e.metadataPlain??{},u.EventType.INTERACTIVE_PROMPT,t);if(!d)return!1;await this.appSyncClient.createEvent({sessionId:t,type:u.EventType.INTERACTIVE_PROMPT,source:u.EventSource.DESKTOP,content:d.content,metadata:d.metadata,...(0,u.raiseFieldsFromRecord)(s),timestamp:(0,u.prepareEventTimestamp)({orderingKey:t}),...d.isEncrypted?{isEncrypted:!0}:{}})}else if(s.mobileActionable=!1,!await this.emitSuppressedPromptCarrier(t,s))return!1;return this.e1NeedsReRaise.delete(s.promptId),n.info("E1: re-raised prompt onto replacement session",{sessionId:t,promptId:s.promptId,actionable:r}),!0}catch(d){if(!o&&c===a)return this.e1NeedsReRaise.add(s.promptId),n.error("E1: re-raise unacked after backoff \u2014 parked for retry-until-ack",{sessionId:t,promptId:s.promptId,error:d instanceof Error?d.message:String(d)}),!1;if(o){let h=Date.now()+Math.min(250*2**(c-1),l);for(;Date.now()<h&&this.isReRaiseTargetCurrent(t);)await new Promise(g=>setTimeout(g,Math.min(50,h-Date.now())))}else await new Promise(h=>setTimeout(h,250*c))}}return!1}async drainPendingE1ReRaises(){if(this.e1NeedsReRaise.size!==0)for(let t of[...this.e1NeedsReRaise]){let e=this.e1PromptRaises.get(t);if(!e){this.e1NeedsReRaise.delete(t);continue}!this.sessionState||e.record.sessionId!==this.sessionState.sessionId||await this.reRaiseOneE1(e.record.sessionId,e)}}async refreshE1Ttls(){await this.drainPendingE1ReRaises();let t=this.sessionState?.sessionId;if(!t)return;let e=[...this.e1PromptRaises.values()].filter(i=>i.record.sessionId===t).map(i=>i.record.promptId);if(e.length!==0)try{let i=await this.appSyncClient.refreshOpenPromptTtl(e);for(let s of i){let r=this.e1PromptRaises.get(s);r&&r.record.sessionId===t&&await this.reRaiseOneE1(t,r)}}catch(i){n.warn("E1: TTL-refresh tick failed (non-fatal, retries next tick)",{error:i instanceof Error?i.message:String(i)})}}startE1TtlRefresh(){this.e1TtlRefreshTimer&&clearInterval(this.e1TtlRefreshTimer),this.e1TtlRefreshTimer=setInterval(()=>{this.refreshE1Ttls()},p.E1_TTL_REFRESH_MS)}stopE1TtlRefresh(){this.e1TtlRefreshTimer&&(clearInterval(this.e1TtlRefreshTimer),this.e1TtlRefreshTimer=null)}isExpectedSessionStateCurrent(t){return!this.isStopping&&this.sessionState===t&&!this.terminalInputBlockedStates.has(t)}sendSessionPinnedInput(t,e){if(!this.isExpectedSessionStateCurrent(t)||this.terminalInputBlockedStates.has(t))return Promise.resolve(!1);let i=this.terminalInputChain.then(async()=>!this.isExpectedSessionStateCurrent(t)||this.terminalInputBlockedStates.has(t)?!1:this.promptResponder.sendInput(t.sessionId,e));return this.terminalInputChain=i.then(()=>{},()=>{}),i}handleEventFromHook(t){if(this.isStopping)return Promise.resolve();let e=this.lifecycleGeneration,i=this.hookEventChain.then(()=>this.processHookEvent(t,e));return this.hookEventChain=i.catch(s=>{n.error("[Hooks] event handler failed",{hookEvent:t.hook_event_name,sessionId:t.session_id,error:s instanceof Error?s.message:String(s)})}),i}async processHookEvent(t,e){if(!this.isLifecycleCurrent(e))return;let{session_id:i,hook_event_name:s,type:r,content:o,metadata:a}=t;n.info("[Hooks] Received event",{sessionId:i,hookEvent:s,type:r,contentLength:o?.length});let l=this.sessionWatcher.getAuthoritativeSessionId();if(l&&l!==i){n.warn("[Hooks] Rejecting event for foreign native session",{hookEvent:s,sessionId:i,authoritativeSessionId:l});return}if(a?.transcript_path){let h=await this.sessionWatcher.bindToSessionTranscript(a.transcript_path,i);if(!this.isLifecycleCurrent(e))return;let g=h.status==="pending"?await h.completion:h;if(!this.isLifecycleCurrent(e))return;if(g.status!=="bound"){n.warn("[Hooks] Rejecting event whose transcript ownership was not proven",{hookEvent:s,sessionId:i,reason:g.reason});return}}else if(l!==i){n.warn("[Hooks] Rejecting event without an owned transcript",{hookEvent:s,sessionId:i});return}if(!this.isLifecycleCurrent(e)||this.sessionWatcher.getAuthoritativeSessionId()!==i){n.warn("[Hooks] Rejecting event after ownership changed during admission",{hookEvent:s,sessionId:i,authoritativeSessionId:this.sessionWatcher.getAuthoritativeSessionId()});return}if(this.hooksActive=!0,s==="SessionStart"){if(this.launchSessionInitPromise&&(await this.launchSessionInitPromise,!this.isLifecycleCurrent(e)))return;if(this.sessionState){if(n.info("[Hooks] SessionStart \u2014 launch session already exists, updating codexSessionId",{existingSessionId:this.sessionState.sessionId,codexSessionId:i}),this.sessionState.codexSessionId=i,this.sessionState.metadata={...this.sessionState.metadata,codexSessionId:i,cliVersion:a?.model||"unknown",modelProvider:a?.model||"unknown",launchSession:void 0},this.appSyncClient.updateSession({sessionId:this.sessionState.sessionId,metadata:this.sessionState.metadata}).catch(h=>n.warn("Failed to update session metadata",{error:h})),await this.startTmuxObserverWithBeacon("session_start_existing"),!this.isLifecycleCurrent(e))return;await this.ensureToolActivityStarted(i)}else{let h={id:i,timestamp:new Date().toISOString(),cwd:a?.cwd||process.cwd(),originator:"hook",cli_version:a?.model||"unknown",instructions:null,source:a?.source||"startup",model_provider:a?.model||"unknown"};await this.ensureSessionStarted(h)}return}if(!this.sessionState){n.warn("[Hooks] Hook event for un-bootstrapped session \u2014 self-healing via session bootstrap (#638)",{hookEvent:s,sessionId:i});let h={id:i,timestamp:new Date().toISOString(),cwd:a?.cwd||process.cwd(),originator:"hook",cli_version:a?.model||"unknown",instructions:null,source:a?.source||"startup",model_provider:a?.model||"unknown"};if(await this.ensureSessionStarted(h),!this.isLifecycleCurrent(e))return;if(!this.sessionState){n.warn("[Hooks] Session still not initialized after self-heal, dropping event",{hook_event_name:s});return}}let c=this.sessionState,d=c.sessionId;if(r==="USER_PROMPT"&&o){let h=this.consumeRecentMobilePrompt(d,o);if(h){n.info("[Hooks] Skipping duplicate USER_PROMPT from mobile",{sessionId:d,matchType:h.matchType,originalLength:h.originalLength,echoLength:h.echoLength});return}}if(s==="PreToolUse"){n.debug("[Hooks] PreToolUse observed; AppSync emission suppressed",{toolName:a?.tool_name||"unknown",sessionId:d});return}if(s==="PermissionRequest"){await this.handlePermissionRequestHook(t,c);return}if(s==="PostToolUse"){if(this.toolActivity?.isActive()&&await this.toolActivity.observeCall({callId:a?.tool_use_id??a?.call_id??a?.callId,toolName:a?.tool_name,toolInput:a?.tool_input})!=="closed")return;let h=this.encryptForEmit(o,a,u.EventType.TOOL_USE,d);if(!h)return;let g=h.content,v=h.metadata,m=h.isEncrypted,y=a?.tool_use_id??a?.call_id??a?.callId,S=typeof y=="string"&&y.length>0&&!!this.sessionKey,I=S?this.getToolActivityLegacyRecorder(d):void 0;I&&S&&I.markLegacyInFlight(y);try{let w=await this.appSyncClient.createEvent({sessionId:d,type:u.EventType.TOOL_USE,source:u.EventSource.DESKTOP,content:g,metadata:v,isEncrypted:m,timestamp:(0,u.prepareEventTimestamp)({orderingKey:d})});I&&S&&((0,u.isPersistedEventResult)(w)?await I.finalizeLegacyOwned([y]):I.clearLegacyInFlight(y))}catch(w){throw I&&S&&I.clearLegacyInFlight(y),w}return}if(r==="ASSISTANT_RESPONSE"||r==="USER_PROMPT"){if(r==="ASSISTANT_RESPONSE"){this.sessionWatcher.getBindSource()==="authoritative"&&this.sessionWatcher.getAuthoritativeSessionId()===i?n.debug("[Hooks] Suppressing Stop final \u2014 JSONL watcher authoritatively bound to this session (Bug #2)"):n.warn("[Hooks] Dropping Stop final after authoritative ownership changed",{authoritativeSessionId:this.sessionWatcher.getAuthoritativeSessionId()});return}let h=this.encryptForEmit(o,void 0,u.EventType.USER_PROMPT,d);if(!h)return;await this.appSyncClient.createEvent({sessionId:d,type:u.EventType.USER_PROMPT,source:u.EventSource.DESKTOP,content:h.content,isEncrypted:h.isEncrypted,timestamp:(0,u.prepareEventTimestamp)({orderingKey:d})});return}}mapToolName(t){return{shell_command:"Bash",shell:"Bash",apply_patch:"Edit",create_file:"Write",read_file:"Read"}[t]||t}trackMobilePrompt(t,e){this.mobilePromptDeduper.track(t,e),n.debug("Tracking mobile prompt for USER_PROMPT echo deduplication",{sessionId:t,promptLength:e.trim().length})}forgetMobilePrompt(t,e){this.mobilePromptDeduper.forget(t,e)}consumeRecentMobilePrompt(t,e){return this.mobilePromptDeduper.consumeIfDuplicate(t,e)}setupEventHandlers(){this.sessionWatcher.on("session-started",async t=>{try{if(this.launchSessionInitPromise&&await this.launchSessionInitPromise,this.sessionState){n.info("[JSONL] Session already active, skipping",{currentSessionId:this.sessionState.sessionId,codexSessionId:t.id}),await this.ensureToolActivityStarted(t.id);return}await this.ensureSessionStarted(t)}catch(e){n.error("[JSONL] session-started handler failed (non-fatal) \u2014 next event retries",{codexSessionId:t?.id,error:String(e)})}}),this.sessionWatcher.on("log-entry",t=>{this.enqueueLogEntry(t)}),this.approvalDetector.on("approval-pending",async t=>{try{await this.handleApprovalPending(t)}catch(e){n.error("approval-pending handler failed (non-fatal)",{error:String(e)})}}),this.tmuxPaneObserver.on("prompt-candidate",async t=>{try{await this.handleTmuxPromptCandidate(t.snapshot)}catch(e){n.error("prompt-candidate handler failed (non-fatal)",{error:String(e)})}}),this.tmuxPaneObserver.on("observer-error",t=>{n.debug("Tmux pane observer error",{error:t})}),this.sessionWatcher.on("error",t=>{n.error("Session watcher error:",t)})}async ensureSessionStarted(t,e){if(this.isStopping||(this.launchSessionInitPromise&&await this.launchSessionInitPromise,this.isStopping)||this.sessionState)return;process.env[ge]==="1"&&await this.bindRecoveryBootstrapIdentity(t.id);let i=this.sessionBootstrapRetryAt-Date.now();if(i>0){n.warn("Session bootstrap is in retry backoff; hook will be retried later",{codexSessionId:t.id,retryDelayMs:i,failureCount:this.sessionBootstrapFailureCount});return}if(this.sessionStartedInitPromise){try{await this.sessionStartedInitPromise}catch{}return}let s=this.handleSessionStarted(t,e);this.sessionStartedInitPromise=s.catch(()=>{});try{await s}finally{this.sessionStartedInitPromise=null}}async handleSessionStarted(t,e){if(this.isStopping)return;let i=this.lifecycleGeneration;n.info("Handling new Codex session",{codexSessionId:t.id}),this.isInitializingSession=!0,this.bufferedLogEntries=[],this.sessionState&&await this.endActiveSession("new-codex-session-started");let s=process.env.CODEX_WORKING_DIRECTORY||t.cwd||process.cwd(),r=this.getOrCreateBootstrapSessionId(),o=this.appSyncClient.getCurrentUserId(),a={codexSessionId:t.id,cliVersion:t.cli_version,modelProvider:t.model_provider},l={sessionKey:null,sessionIsEncrypted:!0};try{let c={sessionId:r,userId:o,agentType:u.AgentType.CODEX,projectPath:s,metadata:a},d=await this.resumeOrCreateBootstrapSession(c);if(r=d.sessionId,!this.isLifecycleCurrent(i)){this.isInitializingSession=!1,await this.retireCancelledBootstrap(r,"session_started");return}this.sessionKey=d.sessionKey,this.sessionKeyGen=d.sessionKeyGen,this.sessionIsEncrypted=!!d.sessionKey,l={sessionKey:d.sessionKey,sessionIsEncrypted:!!d.sessionKey},await P("daemon_init_step_completed",{step:"session_resume_or_create",path:"session_started"})}catch(c){if(this.isInitializingSession=!1,!this.isLifecycleCurrent(i)){let d=this.authoritativeSessionIdFromError(c)??r;await this.retireCancelledBootstrap(d,"session_started_error");return}throw await P("daemon_init_step_failed",{step:"session_resume_or_create",path:"session_started",error_class:c?.name||"Error",error_message:c?.message||String(c)}),c?.code==="ENCRYPTED_SESSION_NO_KEY"&&(this.sessionIsEncrypted=!0,this.sessionKey=null),this.bindBootstrapIdentityFromError(c),this.recordSessionBootstrapFailure(c,"session_started"),n.error("Failed to create/resume session:",c),c}try{this.sessionState={sessionId:r,userId:o,projectPath:s,cwd:t.cwd,createdAt:new Date,subscriptionActive:!1,metadata:a,encryptionSnapshot:l,codexSessionId:t.id,codexLogFile:this.sessionWatcher.getActiveLogFile()||void 0},this.clearSessionBootstrapFailure(),this.bootstrapSessionId=null,this.recoveryBootstrapOwnerBound=!1,await P("daemon_init_step_completed",{step:"session_state_set",path:"session_started"})}catch(c){await P("daemon_init_step_failed",{step:"session_state_set",path:"session_started",error_class:c?.name||"Error",error_message:c?.message||String(c)}),n.error("Failed to set session state:",c)}await this.ensureToolActivityStarted(t.id);try{this.subscribeToMobileEvents(r)?await P("daemon_init_step_completed",{step:"subscribe_mobile_events",path:"session_started"}):await P("daemon_init_step_failed",{step:"subscribe_mobile_events",path:"session_started",error_class:"SubscriptionSetupFailed",error_message:"subscribeToMobileEvents returned false"})}catch(c){await P("daemon_init_step_failed",{step:"subscribe_mobile_events",path:"session_started",error_class:c?.name||"Error",error_message:c?.message||String(c)}),n.error("Failed to subscribe to mobile events:",c)}if(e)try{await e()}catch(c){n.error("E1: pre-heartbeat re-raise hook failed (non-fatal)",{sessionId:r,error:c instanceof Error?c.message:String(c)})}try{await this.drainRetirementOrphans(r)}catch(c){n.error("E1: retirement-orphan drain failed (non-fatal)",{sessionId:r,error:c instanceof Error?c.message:String(c)})}try{await this.appSyncClient.startHeartbeat(r)?await P("daemon_init_step_completed",{step:"heartbeat_start",path:"session_started"}):(await P("daemon_init_step_failed",{step:"heartbeat_start",path:"session_started",error_class:"InitialHeartbeatNotPersisted",error_message:"Backend did not confirm the initial heartbeat mutation"}),n.error("Initial session heartbeat was not persisted",{sessionId:r}))}catch(c){await P("daemon_init_step_failed",{step:"heartbeat_start",path:"session_started",error_class:c?.name||"Error",error_message:c?.message||String(c)}),n.error("Failed to start heartbeat:",c)}try{await this.subscribeToOnApplyUserDecision(r)}catch(c){n.error("Failed to subscribe to onApplyUserDecision (non-fatal)",{error:c})}this.startMobileEndWatcher(r);try{await this.flushBufferedLogEntries(),await P("daemon_init_step_completed",{step:"flush_buffered_entries",path:"session_started"})}catch(c){await P("daemon_init_step_failed",{step:"flush_buffered_entries",path:"session_started",error_class:c?.name||"Error",error_message:c?.message||String(c)}),n.error("Failed to flush buffered log entries:",c),this.bufferedLogEntries=[]}await this.startTmuxObserverWithBeacon("session_started"),this.isInitializingSession=!1}getOrCreateBootstrapSessionId(){return this.bootstrapSessionId||(this.bootstrapSessionId=U.mintBackendSessionId()),this.bootstrapSessionId}async bindRecoveryBootstrapIdentity(t){if(!(process.env[ge]!=="1"||this.bootstrapSessionId||this.sessionState||!t))try{let e=await this.buildToolActivityIntegration().recordedBackendSessionId(t);if(!e){n.warn("Recovery daemon found no durable backend owner; a new session will be created",{rolloutId:t});return}if(!/^codex-[A-Za-z0-9_-]{1,180}$/.test(e)){n.warn("Recovery daemon ignored invalid durable backend owner",{rolloutId:t});return}this.bootstrapSessionId=e,this.recoveryBootstrapOwnerBound=!0,n.info("Recovery daemon reclaiming existing backend session",{rolloutId:t,sessionId:e})}catch(e){n.warn("Recovery daemon could not read the durable rollout owner; a new session will be created",{rolloutId:t,error:e instanceof Error?e.message:String(e)})}}isSessionIdReservedRejection(t){return(t instanceof Error?t.message:String(t)).includes("SESSION_ID_RESERVED")}async resumeOrCreateBootstrapSession(t){try{return await this.resumeBackendSession(t,this.appSyncClient,n)}catch(e){if(!this.recoveryBootstrapOwnerBound||!this.isSessionIdReservedRejection(e))throw e;return n.warn("Recovery discarded a permanently reserved pending create; retrying the durable rollout owner",{sessionId:t.sessionId}),this.resumeBackendSession(t,this.appSyncClient,n)}}startTmuxLifecycleMonitor(){if(this.tmuxLifecycleTimer)return;let t=process.env[$];t&&(this.tmuxLifecycleTimer=setInterval(()=>{this.checkTmuxLifecycle(t)},Wt),this.tmuxLifecycleTimer.unref?.(),this.checkTmuxLifecycle(t))}async tmuxSessionExists(t){try{return await zt("tmux",["has-session","-t",t]),!0}catch{return!1}}async checkTmuxLifecycle(t){if(!(this.isStopping||this.tmuxLifecycleCheckInFlight)){this.tmuxLifecycleCheckInFlight=!0;try{if(await this.tmuxSessionExists(t)){this.tmuxLifecycleObservedAlive=!0;return}if(!this.tmuxLifecycleObservedAlive||this.isStopping)return;this.stopTmuxLifecycleMonitor(),n.info("Native Codex tmux session ended; stopping companion daemon",{tmuxSession:t}),this.stop().then(()=>process.exit(0),e=>{n.error("Failed to stop companion daemon after native session end",{error:e instanceof Error?e.message:String(e)}),process.exit(1)})}finally{this.tmuxLifecycleCheckInFlight=!1}}}stopTmuxLifecycleMonitor(){this.tmuxLifecycleTimer&&(clearInterval(this.tmuxLifecycleTimer),this.tmuxLifecycleTimer=null)}isLifecycleCurrent(t){return!this.isStopping&&this.lifecycleGeneration===t}authoritativeSessionIdFromError(t){let e=t?.authoritativeSessionId;return typeof e=="string"&&e.length>0?e:null}async retireCancelledBootstrap(t,e){this.appSyncClient.stopHeartbeat(t),this.appSyncClient.cleanupSubscription(t);try{await this.appSyncClient.updateSession({sessionId:t,status:u.SessionStatus.INACTIVE}),n.info("Cancelled bootstrap row marked INACTIVE",{sessionId:t,path:e})}catch(i){n.warn("Cancelled bootstrap row could not be marked INACTIVE",{sessionId:t,path:e,error:i instanceof Error?i.message:String(i)})}}bindBootstrapIdentityFromError(t){let e=t?.authoritativeSessionId;typeof e!="string"||e.length===0||(this.bootstrapSessionId=e,n.info("Retained authoritative backend identity from failed bootstrap",{sessionId:e}))}recoverRetiredBackendSession(t){if(this.retiredSessionRecoveryPromise)return this.retiredSessionRecoveryPromise;if(this.isStopping)return Promise.resolve();let e=(async()=>{if(this.launchSessionInitPromise&&await this.launchSessionInitPromise,this.sessionStartedInitPromise&&await this.sessionStartedInitPromise,this.isStopping)return;let i=this.sessionState;if(!i||i.sessionId!==t)return;let s={id:i.codexSessionId||t,timestamp:new Date().toISOString(),cwd:i.cwd||i.projectPath,originator:"codevibe-retired-session-recovery",cli_version:String(i.metadata?.cliVersion??"unknown"),instructions:null,source:"codevibe",model_provider:String(i.metadata?.modelProvider??"unknown")};n.warn("Backend retired live Codex session; creating replacement row",{sessionId:t,codexSessionId:s.id}),this.parkRetirementOrphans(t),await this.endActiveSession("backend-generation-retired"),this.bootstrapSessionId=null,this.clearSessionBootstrapFailure(),await this.ensureSessionStarted(s)})();return this.retiredSessionRecoveryPromise=e.finally(()=>{this.retiredSessionRecoveryPromise=null}),this.retiredSessionRecoveryPromise}recordSessionBootstrapFailure(t,e){this.sessionBootstrapFailureCount+=1;let i=t instanceof Error?t.message:String(t),r=i.includes("SESSION_LIMIT_EXCEEDED")?6e4:Math.min(5e3*2**(this.sessionBootstrapFailureCount-1),6e4);this.sessionBootstrapRetryAt=Date.now()+r,n.warn("Session bootstrap retry scheduled",{path:e,requestedSessionId:this.bootstrapSessionId,failureCount:this.sessionBootstrapFailureCount,delayMs:r,error:i})}clearSessionBootstrapFailure(){this.sessionBootstrapFailureCount=0,this.sessionBootstrapRetryAt=0}async flushBufferedLogEntries(){if(this.bufferedLogEntries.length===0)return;let t=this.bufferedLogEntries;this.bufferedLogEntries=[],n.info("Flushing buffered log entries after session initialization",{count:t.length,sessionId:this.sessionState?.sessionId});let e=Promise.resolve();for(let i of t)e=this.enqueueLogEntry(i);await e.catch(()=>{})}enqueueLogEntry(t){let e=this.logEntryChain.then(()=>this.handleLogEntry(t));return this.logEntryChain=e.catch(i=>{n.error("[JSONL] log entry handler failed",{type:t.type,err:String(i)})}),e}async handleLogEntry(t){if(!this.sessionState){if(this.isInitializingSession){this.bufferedLogEntries.push(t),n.debug("Buffering log entry until session initialization completes",{type:t.type,bufferedCount:this.bufferedLogEntries.length});return}n.warn("Received log entry but no active session");return}let e=this.sessionState,i=e.sessionId,{sessionKey:s,sessionIsEncrypted:r}=e.encryptionSnapshot;if(t.type==="response_item"&&t.payload){let m=t.payload.type;if(m==="function_call"||m==="custom_tool_call")this.approvalDetector.onToolCallStart(t.payload.call_id,t.payload.name,t.payload.arguments||t.payload.input||"");else if(m==="function_call_output"||m==="custom_tool_call_output"){let y=this.approvalDetector.getPendingCalls().find(I=>I.callId===t.payload.call_id),S=y?this.buildApprovalDedupeKey(this.buildApprovalPromptContextFromPendingCall(y)):void 0;if(this.approvalDetector.onToolCallComplete(t.payload.call_id),await this.clearResolvedInteractivePrompt(t.payload.call_id,S),!this.isExpectedSessionStateCurrent(e))return}}let o=ke(t,i);if(!o)return;o.timestamp=(0,u.prepareEventTimestamp)({orderingKey:i,agentClock:t.timestamp});let a=t.payload?.type,l=!1;if(this.toolActivity?.isActive()&&o.type===u.EventType.TOOL_USE){let m=a==="function_call"||a==="custom_tool_call",y=a==="function_call_output"||a==="custom_tool_call_output";if(m&&typeof t.payload?.call_id=="string"){let S=this.sessionWatcher.getActiveLogFile();S&&this.toolActivity.bindTranscript(S);let I=await this.toolActivity.observeCall({callId:t.payload.call_id,toolName:o.metadata?.toolName??o.metadata?.tool_name,toolInput:o.metadata?.toolInput??o.metadata?.tool_input,ts:t.timestamp});if(!this.isExpectedSessionStateCurrent(e)||I!=="closed")return;l=!0}if(y)return}if(this.toolActivity?.isActive()&&(a==="user_message"||a==="agent_message")&&(await this.toolActivity.flush().catch(m=>n.warn("[tool-activity] codex boundary flush failed",{sessionId:i,err:String(m)})),!this.isExpectedSessionStateCurrent(e)))return;let c=a==="function_call"||a==="function_call_output",d=o.type===u.EventType.USER_PROMPT||o.type===u.EventType.ASSISTANT_RESPONSE||c&&(o.type===u.EventType.TOOL_USE||o.type===u.EventType.INTERACTIVE_PROMPT);if(!this.hooksActive&&d&&(await this.emitHooksCompatibilityModeNotice(e),!this.isExpectedSessionStateCurrent(e)))return;if(this.hooksActive){if(o.type===u.EventType.USER_PROMPT){n.debug("[JSONL] Skipping USER_PROMPT \u2014 hooks deliver it",{type:o.type});return}if(c&&(o.type===u.EventType.TOOL_USE||o.type===u.EventType.INTERACTIVE_PROMPT)&&!l){n.debug("[JSONL] Skipping function_call \u2014 hooks deliver this",{type:o.type,tool:t.payload?.name});return}}if(o.type===u.EventType.USER_PROMPT&&o.source===u.EventSource.DESKTOP){let m=this.consumeRecentMobilePrompt(i,o.content);if(m){n.info("[JSONL] Skipping duplicate USER_PROMPT from mobile",{sessionId:i,matchType:m.matchType,originalLength:m.originalLength,echoLength:m.echoLength});return}}if(this.sessionState!==e){n.warn("[JSONL] Dropping event from stale session generation (#638)",{sessionId:i,currentSessionId:this.sessionState?.sessionId});return}let h=t.payload?.call_id,g=o.type===u.EventType.TOOL_USE&&typeof h=="string"&&h.length>0&&!!s,v=g?this.getToolActivityLegacyRecorder(i):void 0;v&&g&&v.markLegacyInFlight(h);try{let m=(0,u.encryptForEmit)(s,r,o.content,o.metadata);if(!m){n.error("No session key for ENCRYPTED session \u2014 dropping JSONL event (fail-closed, #638)",{type:o.type,sessionId:i});return}o.content=m.content,o.metadata=m.metadata,m.isEncrypted&&(o.isEncrypted=!0,n.debug("Event encrypted",{type:o.type}));let y=await this.appSyncClient.createEvent(o);n.debug("Event synced to backend",{type:o.type,encrypted:m.isEncrypted}),v&&g&&((0,u.isPersistedEventResult)(y)?await v.finalizeLegacyOwned([h]):v.clearLegacyInFlight(h))}catch(m){v&&g&&v.clearLegacyInFlight(h),n.error("Failed to sync event:",m)}}async handlePermissionRequestHook(t,e){if(!this.isExpectedSessionStateCurrent(e))return;let i=t.metadata||{},s=typeof i.tool_name=="string"?i.tool_name:"Tool",r=i.tool_input,o=this.stringifyToolInput(r),a={toolName:s,toolInput:r,rawInput:o,filePath:this.extractFilePathFromToolInput(s,r,o),diff:s==="apply_patch"?o:void 0,hint:this.buildPermissionRequestHint(s,r,o)},l=this.buildToolDetailsForInteractivePrompt(a),c=l.tool_name||this.mapToolNameForApproval(s),d=l.tool_input||this.buildFallbackToolInput(a),h=!!(c&&d),g=await this.tryParsePermissionRequestPromptFromTmux(H=>this.parsedPromptMatchesApprovalContext(H,a));if(!this.isExpectedSessionStateCurrent(e)){n.warn("[Hooks] Dropping stale PermissionRequest after session generation changed",{sessionId:e.sessionId,currentSessionId:this.sessionState?.sessionId});return}let v=g?.parsedPrompt??null;if(!v){n.warn("[Hooks] Suppressing PermissionRequest mobile prompt because exact Codex options are unavailable",{sessionId:e.sessionId,toolName:s,hint:a.hint}),this.raiseSuppressedBadge(e.sessionId,this.buildApprovalDedupeKey({toolName:s,toolInput:r,filePath:a.filePath,rawInput:o,hint:a.hint}));return}let m=this.buildApprovalDedupeKey({toolName:s,toolInput:r,filePath:a.filePath,rawInput:o,hint:a.hint});if(m&&this.hasRecentlyResolvedApprovalDedupeKey(e.sessionId,m)){n.info("[Hooks] Skipping PermissionRequest prompt; same approval was already answered recently",{sessionId:e.sessionId,toolName:s,dedupeKey:m});return}let y=this.buildCodexPromptPresentation(v);if(!y){n.warn("[Hooks] Suppressing PermissionRequest mobile prompt because Codex option hotkeys are unavailable",{sessionId:e.sessionId,toolName:s,hint:a.hint}),this.raiseSuppressedBadge(e.sessionId,m);return}let S=this.buildPermissionPromptId(i,s,r,o),I=this.buildApprovalPromptContent(y.content,{toolName:c,toolInput:d,hint:a.hint,filePath:a.filePath}),w={promptId:S,kind:y.kind,options:y.options,submitMap:y.submitMap,promptText:I,createdAt:Date.now(),source:"permission_hook",dedupeKey:m,requiresFollowUpText:y.requiresFollowUpText},T={isApprovalHint:!0,toolName:s,toolInput:r,hint:a.hint,filePath:a.filePath,diff:a.diff,rawInput:o,tool_name:c,tool_input:d,has_details:h,options:y.options,instructions:y.instructions,prompt_source:v?"permission_hook_tmux":"permission_hook",permission_mode:i.permission_mode,turn_id:i.turn_id,dedupe_key:m};n.info("[Hooks] Sending PermissionRequest interactive prompt",{sessionId:e.sessionId,toolName:s,promptId:S,promptSource:T.prompt_source,dedupeKey:m}),await this.enqueueOrEmitInteractivePrompt({prompt:w,content:I,metadata:T,ownerState:e})}async emitHooksCompatibilityModeNotice(t){if(this.hooksCompatibilityNoticeSent||!this.isExpectedSessionStateCurrent(t))return;this.hooksCompatibilityNoticeSent=!0;let e="Codex hooks are not active. CodeVibe is using compatibility mode: mobile approvals still mirror the desktop prompt, but timeline events may be delayed or incomplete. Open Codex /hooks and trust/enable CodeVibe hooks for full fidelity.",i={compatibility_mode:!0,reason:"codex_hooks_inactive",action:"trust_enable_codevibe_hooks"};n.warn("Codex hooks inactive; running in compatibility mode",{sessionId:t.sessionId});let s=this.encryptForEmit(e,i,u.EventType.NOTIFICATION,t.sessionId);if(s)try{await this.appSyncClient.createEvent({sessionId:t.sessionId,type:u.EventType.NOTIFICATION,source:u.EventSource.DESKTOP,content:s.content,metadata:s.metadata,timestamp:(0,u.prepareEventTimestamp)({orderingKey:t.sessionId}),...s.isEncrypted?{isEncrypted:!0}:{}})}catch(r){n.warn("Failed to emit hooks compatibility mode notification",{error:r})}}async handleApprovalPending(t){let e=this.sessionState;if(!e)return;let i=this.buildApprovalDedupeKey(t);if(i&&this.hasRecentlyResolvedApprovalDedupeKey(e.sessionId,i)){n.info("Skipping heuristic approval prompt; same approval was already answered recently",{callId:t.callId,toolName:t.toolName,dedupeKey:i});return}if(i&&this.hasActiveOrQueuedPermissionHookPrompt(i)){this.mergeCallIdIntoPromptByDedupeKey(i,t.callId),n.info("Skipping heuristic approval prompt; PermissionRequest hook already emitted it",{callId:t.callId,toolName:t.toolName,dedupeKey:i});return}n.info("Sending approval pending interactive prompt",t);try{let s=await this.tryParseInteractivePromptFromTmux(),r=s?.parsedPrompt??null,o=this.buildToolDetailsForInteractivePrompt(t,s?.snapshot),a=o.tool_name||this.mapToolNameForApproval(t.toolName),l=o.tool_input||this.buildFallbackToolInput(t),c=!!(a&&l);if(!r){n.warn("Suppressing heuristic approval prompt because exact Codex options are unavailable",{callId:t.callId,toolName:t.toolName,hint:t.hint}),this.raiseSuppressedBadge(e.sessionId,i);return}if(!this.parsedPromptMatchesApprovalContext(r,t)){n.warn("Suppressing heuristic approval prompt because parsed tmux prompt does not match the active tool",{callId:t.callId,toolName:t.toolName,hint:t.hint,parsedPromptText:r.promptText}),this.raiseSuppressedBadge(e.sessionId,i);return}let d=this.buildCodexPromptPresentation(r);if(!d){n.warn("Suppressing heuristic approval prompt because Codex option hotkeys are unavailable",{callId:t.callId,toolName:t.toolName,hint:t.hint}),this.raiseSuppressedBadge(e.sessionId,i);return}let h=d.options,g=this.buildApprovalPromptContent(d.content,{toolName:a,toolInput:l,hint:t.hint,filePath:t.filePath}),v={promptId:t.callId,callId:t.callId,kind:d.kind,options:h,submitMap:d.submitMap,promptText:d.promptText,createdAt:Date.now(),source:r?"tmux":"heuristic",dedupeKey:i,requiresFollowUpText:d.requiresFollowUpText},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:a,tool_input:l,has_details:c,options:h,instructions:d.instructions,prompt_source:r?"tmux":"heuristic",dedupe_key:i};n.debug("Interactive prompt (pre-encryption)",{sessionId:e.sessionId,callId:t.callId,contentPreview:g.substring(0,200),toolDetails:o,metadata:m}),await this.enqueueOrEmitInteractivePrompt({prompt:v,content:g,metadata:m,ownerState:e})}catch(s){n.error("Failed to send approval interactive prompt:",s)}}async handleTmuxPromptCandidate(t){let e=this.sessionState;if(!e){this.tmuxPaneObserver.resetLastPromptHash();return}let i=(0,M.parseInteractivePrompt)(t);if(!i){this.raiseSuppressedBadge(e.sessionId,null);return}let s=this.buildCodexPromptPresentation(i);if(!s){n.warn("Skipping tmux-detected prompt because Codex option hotkeys are unavailable",{parsedPromptText:i.promptText}),this.raiseSuppressedBadge(e.sessionId,null);return}let r=this.getMostRecentPendingToolCall();if(!r){if(await new Promise(w=>setTimeout(w,500)),!this.isExpectedSessionStateCurrent(e)){n.warn("Dropping tmux prompt candidate after session generation changed",{sessionId:e.sessionId,currentSessionId:this.sessionState?.sessionId});return}r=this.getMostRecentPendingToolCall()}let o=r?this.buildApprovalPromptContextFromPendingCall(r):null;if(o&&!this.parsedPromptMatchesApprovalContext(i,o)){n.warn("Skipping tmux-detected prompt because parsed prompt does not match pending tool call",{callId:r?.callId,toolName:r?.name,parsedPromptText:i.promptText}),this.raiseSuppressedBadge(e.sessionId,null);return}let a=o?null:this.buildApprovalPromptContextFromParsedPrompt(i),l=o||a;if(!l){n.warn("Skipping tmux-detected prompt because no tool context could be derived from the parsed prompt",{parsedPromptText:i.promptText}),this.raiseSuppressedBadge(e.sessionId,null);return}let c=l?this.buildApprovalDedupeKey(l):void 0;if(c&&this.hasRecentlyResolvedApprovalDedupeKey(e.sessionId,c)){n.info("Skipping tmux-detected prompt; same approval was already answered recently",{promptText:i.promptText,dedupeKey:c});return}if(c&&this.hasActiveOrQueuedPermissionHookPrompt(c)){r?.callId&&this.mergeCallIdIntoPromptByDedupeKey(c,r.callId),n.info("Skipping tmux-detected prompt; PermissionRequest hook already emitted it",{promptText:i.promptText,dedupeKey:c});return}let d=l?this.buildToolDetailsForInteractivePrompt(l,t):{},h=d.tool_name||this.mapToolNameForApproval(r?.name),g=d.tool_input||(l?this.buildFallbackToolInput(l):void 0),v=!!(h&&g),y={promptId:r?.callId||(0,Je.v4)(),callId:r?.callId,kind:s.kind,options:s.options,submitMap:s.submitMap,promptText:s.promptText,createdAt:Date.now(),source:"tmux",dedupeKey:c,requiresFollowUpText:s.requiresFollowUpText},S={options:s.options,instructions:s.instructions,prompt_source:"tmux_live",tool_name:h,tool_input:g,has_details:v,dedupe_key:c},I=this.buildApprovalPromptContent(s.content,{toolName:h,toolInput:g,hint:l?.hint,filePath:l?.filePath});try{await this.enqueueOrEmitInteractivePrompt({prompt:y,content:I,metadata:S,ownerState:e})&&n.info("Sent tmux-detected interactive prompt",{sessionId:e.sessionId,promptText:i.promptText,kind:i.kind})}catch(w){n.error("Failed to send tmux-detected interactive prompt",{error:w})}}async enqueueOrEmitInteractivePrompt(t){if(!this.isExpectedSessionStateCurrent(t.ownerState))return n.warn("Dropping interactive prompt from stale session generation",{sessionId:t.ownerState.sessionId,currentSessionId:this.sessionState?.sessionId,source:t.prompt.source}),!1;if(t.prompt.dedupeKey&&this.hasRecentlyResolvedApprovalDedupeKey(t.ownerState.sessionId,t.prompt.dedupeKey))return n.debug("Skipping interactive prompt emission; same approval was already answered recently",{source:t.prompt.source,callId:t.prompt.callId,dedupeKey:t.prompt.dedupeKey}),!1;let e=this.pendingInteractivePrompt;if(!e){this.pendingInteractivePrompt=t.prompt;try{return await this.emitInteractivePromptEvent(t),!0}catch(i){throw this.pendingInteractivePrompt?.promptId===t.prompt.promptId&&(this.pendingInteractivePrompt=null),i}}return this.isSameInteractivePrompt(e,t.prompt)?(!e.callId&&t.prompt.callId&&(e.callId=t.prompt.callId,n.debug("Merged callId into existing interactive prompt",{source:e.source,callId:t.prompt.callId,promptText:e.promptText})),n.debug("Skipping duplicate interactive prompt emission",{existingSource:e.source,candidateSource:t.prompt.source,existingCallId:e.callId,candidateCallId:t.prompt.callId,promptText:t.prompt.promptText}),!1):this.queuedInteractivePrompts.some(i=>this.isSameInteractivePrompt(i.prompt,t.prompt))?(n.debug("Skipping duplicate queued interactive prompt",{candidateSource:t.prompt.source,candidateCallId:t.prompt.callId,promptText:t.prompt.promptText}),!1):(this.queuedInteractivePrompts.push(t),n.info("Queued interactive prompt behind active prompt",{activeCallId:e.callId,queuedCallId:t.prompt.callId,queueLength:this.queuedInteractivePrompts.length}),!1)}async emitInteractivePromptEvent(t){if(!this.isExpectedSessionStateCurrent(t.ownerState))return;let e=t.ownerState.sessionId,i=this.e1PromptRaises.get(t.prompt.promptId),s=i?i.record:this.newE1Raise(e,!0);t.prompt.promptId=s.promptId,t.prompt.dedupeKey&&this.e1ContentKeyIndex.set(`${e}::${t.prompt.dedupeKey}`,s.promptId),this.stashE1RaiseContent(s.promptId,t.content,t.metadata??{});let r=this.encryptForEmit(t.content,t.metadata,u.EventType.INTERACTIVE_PROMPT,e);r&&await this.appSyncClient.createEvent({sessionId:e,type:u.EventType.INTERACTIVE_PROMPT,source:u.EventSource.DESKTOP,content:r.content,metadata:r.metadata,...(0,u.raiseFieldsFromRecord)(s),timestamp:(0,u.prepareEventTimestamp)({orderingKey:e}),...r.isEncrypted?{isEncrypted:!0}:{}})}async emitNextQueuedInteractivePrompt(){if(this.pendingInteractivePrompt||this.queuedInteractivePrompts.length===0)return;let t=this.queuedInteractivePrompts.shift();if(t){if(!this.isExpectedSessionStateCurrent(t.ownerState)){n.warn("Discarding queued prompt from stale session generation",{sessionId:t.ownerState.sessionId,currentSessionId:this.sessionState?.sessionId}),await this.emitNextQueuedInteractivePrompt();return}this.pendingInteractivePrompt=t.prompt;try{await this.emitInteractivePromptEvent(t),n.info("Emitted next queued interactive prompt",{callId:t.prompt.callId,queueLength:this.queuedInteractivePrompts.length})}catch(e){this.pendingInteractivePrompt=null,n.error("Failed to emit queued interactive prompt",{error:e}),await this.emitNextQueuedInteractivePrompt()}}}removeQueuedInteractivePromptByCallId(t){let e=this.queuedInteractivePrompts.length,i=this.queuedInteractivePrompts.filter(s=>s.prompt.callId===t);for(let s of i)this.rememberResolvedApprovalDedupeKey(s.ownerState.sessionId,s.prompt.dedupeKey);this.queuedInteractivePrompts=this.queuedInteractivePrompts.filter(s=>s.prompt.callId!==t),this.queuedInteractivePrompts.length!==e&&n.debug("Removed resolved tool call from interactive prompt queue",{callId:t,removed:e-this.queuedInteractivePrompts.length})}async clearResolvedInteractivePrompt(t,e){let i=this.pendingInteractivePrompt;if(i&&(i.callId===t||e!==void 0&&i.dedupeKey===e)){this.rememberResolvedApprovalDedupeKey(this.sessionState?.sessionId,i.dedupeKey),this.pendingInteractivePrompt=null,await this.emitNextQueuedInteractivePrompt();return}this.removeQueuedInteractivePromptByCallId(t),e!==void 0&&this.removeQueuedInteractivePromptByDedupeKey(e)}removeQueuedInteractivePromptByDedupeKey(t){let e=this.queuedInteractivePrompts.length,i=this.queuedInteractivePrompts.filter(s=>s.prompt.dedupeKey===t);for(let s of i)this.rememberResolvedApprovalDedupeKey(s.ownerState.sessionId,s.prompt.dedupeKey);this.queuedInteractivePrompts=this.queuedInteractivePrompts.filter(s=>s.prompt.dedupeKey!==t),this.queuedInteractivePrompts.length!==e&&n.debug("Removed resolved deduped tool call from interactive prompt queue",{dedupeKey:t,removed:e-this.queuedInteractivePrompts.length})}mergeCallIdIntoPromptByDedupeKey(t,e){let i=this.pendingInteractivePrompt;if(i?.dedupeKey===t&&!i.callId){i.callId=e,n.debug("Merged callId into active deduped interactive prompt",{source:i.source,callId:e,dedupeKey:t});return}let s=this.queuedInteractivePrompts.find(r=>r.prompt.dedupeKey===t&&!r.prompt.callId);s&&(s.prompt.callId=e,n.debug("Merged callId into queued deduped interactive prompt",{source:s.prompt.source,callId:e,dedupeKey:t}))}isSameInteractivePrompt(t,e){return Date.now()-t.createdAt>jt?!1:t.callId&&e.callId?t.callId===e.callId:t.dedupeKey&&e.dedupeKey?t.dedupeKey===e.dedupeKey:t.kind===e.kind&&this.normalizePromptDedupeText(t.promptText)===this.normalizePromptDedupeText(e.promptText)}normalizePromptDedupeText(t){return t.replace(/\s+/g," ").trim().toLowerCase()}async startTmuxObserver(){let t=process.env.CODEVIBE_CODEX_TMUX_SESSION;if(!t)return n.debug("Skipping tmux pane observer start - no tmux session in environment"),!0;try{return await this.tmuxPaneObserver.start(t),!0}catch(e){return n.warn("Failed to start tmux pane observer",{tmuxSession:t,error:e}),!1}}async startTmuxObserverWithBeacon(t){try{await this.startTmuxObserver()?await P("daemon_init_step_completed",{step:"tmux_observer_start",path:t}):await P("daemon_init_step_failed",{step:"tmux_observer_start",path:t,error_class:"TmuxObserverStartFailed",error_message:"tmuxPaneObserver.start threw (see plugin log)"})}catch(e){await P("daemon_init_step_failed",{step:"tmux_observer_start",path:t,error_class:e?.name||"Error",error_message:e?.message||String(e)}),n.error("Failed to start tmux observer:",e)}}async tryParseInteractivePromptFromTmux(){try{let t=await this.tmuxPaneObserver.captureSnapshot(),e=this.tmuxPaneObserver.extractActiveApprovalBlock(t),i=e?(0,M.parseInteractivePrompt)(e):null;return n.debug("tmux prompt parse result",{parsed:!!i,kind:i?.kind,promptText:i?.promptText,isolated:!!e,snapshotPreview:this.summarizePromptSnapshot(e??t)}),{parsedPrompt:i,snapshot:e??t}}catch(t){return n.debug("tmux prompt parsing unavailable",{error:t}),null}}async tryParsePermissionRequestPromptFromTmux(t){let e=await this.tryParseInteractivePromptFromTmux();if(e?.parsedPrompt&&(!t||t(e.parsedPrompt,e.snapshot)))return e;e?.parsedPrompt&&n.warn("Ignoring parsed PermissionRequest prompt because it does not match the active tool",{promptText:e.parsedPrompt.promptText}),await new Promise(s=>setTimeout(s,250));let i=await this.tryParseInteractivePromptFromTmux();return i?.parsedPrompt&&(!t||t(i.parsedPrompt,i.snapshot))?i:(i?.parsedPrompt&&n.warn("Ignoring retried PermissionRequest prompt because it does not match the active tool",{promptText:i.parsedPrompt.promptText}),i?.snapshot||e?.snapshot?{parsedPrompt:null,snapshot:i?.snapshot||e?.snapshot||""}: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}}buildCodexPromptPresentation(t){let e=this.buildPromptPresentation(t),i=this.buildSubmitMapFromCodexOptionHotkeys(e.options);return i?{...e,submitMap:i,instructions:this.buildCodexPermissionInstructions(e.options),requiresFollowUpText:this.optionsRequireFollowUpText(e.options)}:null}buildSubmitMapFromCodexOptionHotkeys(t){let e={};for(let i of t){let s=i.text.match(/\(([^)]+)\)\s*$/)?.[1]?.trim().toLowerCase();if(!s||!Vt.test(s))return null;s==="esc"||s==="escape"?e[i.number]=K:e[i.number]=s}return e}buildCodexPermissionInstructions(t){let e=t.find(o=>/\((?:y|yes)\)\s*$/i.test(o.text)),i=t.find(o=>/\(p\)\s*$/i.test(o.text)),s=t.find(o=>/\((?:esc|escape)\)\s*$/i.test(o.text)),r=[];return e&&r.push(`${e.number} to approve`),i&&r.push(`${i.number} to persist the desktop allow rule`),s&&r.push(`${s.number} followed by what to change`),r.length>0?`Reply with ${r.join(", ")}`:"Reply with the number of the option you want"}optionsRequireFollowUpText(t){return t.some(e=>/what to (?:do differently|change)|instructions/i.test(e.text))}getMostRecentPendingToolCall(){let t=this.approvalDetector.getPendingCalls();return t.length===0?null:t.reduce((e,i)=>i.timestamp>e.timestamp?i:e)}hasActiveOrQueuedPermissionHookPrompt(t){let e=this.pendingInteractivePrompt;return e?.source==="permission_hook"&&e.dedupeKey===t?!0:this.queuedInteractivePrompts.some(i=>i.prompt.source==="permission_hook"&&i.prompt.dedupeKey===t)}hasRecentlyResolvedApprovalDedupeKey(t,e){return t?(this.pruneResolvedApprovalDedupeKeys(),this.resolvedApprovalDedupeKeys.has(`${t}::${e}`)):!1}rememberResolvedApprovalDedupeKey(t,e){!t||!e||(this.pruneResolvedApprovalDedupeKeys(),this.resolvedApprovalDedupeKeys.set(`${t}::${e}`,Date.now()))}pruneResolvedApprovalDedupeKeys(){let t=Date.now()-Gt;for(let[e,i]of this.resolvedApprovalDedupeKeys.entries())i<t&&this.resolvedApprovalDedupeKeys.delete(e)}buildApprovalDedupeKey(t){let e=t.toolName||"Tool",i=this.firstString(t.toolInput?.command,t.toolInput?.cmd,t.rawInput);if(i)return`command:${this.hashDedupeValue(`${this.mapToolNameForApproval(e)||e}:${i.trim()}`)}`;let s=this.firstString(t.filePath,t.toolInput?.file_path,t.toolInput?.path,t.toolInput?.filePath);if(s)return`file:${this.hashDedupeValue(`${e}:${s}`)}`;let r=this.firstString(t.hint);if(r)return`hint:${this.hashDedupeValue(`${e}:${r}`)}`}hashDedupeValue(t){return ye.createHash("sha256").update(t.replace(/\s+/g," ").trim().toLowerCase()).digest("hex").slice(0,16)}buildPermissionPromptId(t,e,i,s){let r=typeof t.turn_id=="string"&&t.turn_id.trim().length>0?t.turn_id.trim():void 0,o=ye.createHash("sha256").update(`${e}:${s||this.stringifyToolInput(i)}`).digest("hex").slice(0,12);return`permission-${r||"turn"}-${o}`}buildPermissionRequestHint(t,e,i){let s=this.firstString(e?.command,e?.cmd);if(s)return`Command: ${this.truncateApprovalDetail(s,80)}`;let r=this.extractFilePathFromToolInput(t,e,i);return r?`File: ${r}`:`Tool: ${this.mapToolNameForApproval(t)||t}`}extractFilePathFromToolInput(t,e,i){let s=this.firstString(e?.file_path,e?.path,e?.filePath);if(s)return s;if(t==="apply_patch"&&i){let r=i.match(/\*\*\* (?:Update|Add|Delete) File: (.+)/);if(r)return r[1].trim()}}stringifyToolInput(t){if(t!=null){if(typeof t=="string")return t;try{return JSON.stringify(t)}catch{return String(t)}}}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}`}}buildApprovalPromptContextFromParsedPrompt(t){let e=this.extractShellCommandFromPromptText(t.promptText);return e?{toolName:"Bash",toolInput:{command:e},rawInput:e,hint:`Command: ${this.truncateApprovalDetail(e,80)}`}:null}parsedPromptMatchesApprovalContext(t,e){let i=this.firstString(e.toolInput?.command,e.toolInput?.cmd);if(i){let r=Se(t.promptText);if(!r||r.length===0)return!1;let o=this.normalizeApprovalCommand(i);return this.normalizeApprovalCommand(r.join(" "))===o||r.length>1&&this.normalizeApprovalCommand(r.join(""))===o}let s=this.firstString(e.filePath,e.toolInput?.file_path,e.toolInput?.path,e.toolInput?.filePath);return s?t.promptText.includes(s):!1}normalizeApprovalCommand(t){return t.replace(/\s+/g," ").trim()}extractShellCommandFromPromptText(t){return et(t)}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"}buildApprovalPromptContent(t,e){let i=t.trim(),s=i==="Codex is waiting for approval.",r=/^\$\s+/.test(i),o=e.toolName?`${e.toolName} requires approval.`:"Codex is waiting for approval.",a=[s||r||!i?o:i],l=typeof e.toolInput?.command=="string"?e.toolInput.command.trim():void 0;if(l&&!a.join(`
20
+ `).includes(l))return a.push("Command:",this.truncateApprovalDetail(l,240)),a.join(`
21
+ `);let c=e.filePath||e.toolInput?.file_path;return typeof c=="string"&&c.length>0&&!a.join(`
22
+ `).includes(c)?(a.push(`File: ${c}`),a.join(`
23
+ `)):(e.hint&&!a.join(`
24
+ `).includes(e.hint)&&a.push(e.hint),a.join(`
25
+ `))}truncateApprovalDetail(t,e){return t.length>e?`${t.slice(0,e-3)}...`:t}summarizePromptSnapshot(t){return t.split(`
26
+ `).map(e=>e.trimEnd()).filter(e=>e.length>0).slice(-12).map(e=>e.slice(0,160)).join(`
27
+ `)}translatePromptResponse(t){let e=this.pendingInteractivePrompt;if(!e)return{primaryInput:t};let s=t.trim().match(/^(\d+)(?:[,.:;\-\s]+([\s\S]+))?$/);if(!s)return{primaryInput:t};let r=s[1],o=s[2]?.trim(),a=e.submitMap[r];return a?e.requiresFollowUpText&&o?{primaryInput:a,followUpInput:o}:{primaryInput:a}:{primaryInput:t}}getEventPromptId(t){let e=t.promptId;return typeof e=="string"&&e.trim().length>0?e.trim():null}isApprovalResponseLike(t){let e=t.trim().toLowerCase();return/^(?:\d+|y|yes|n|no)(?:[\s,.:;-].*)?$/.test(e)}async emitRejectedPromptResponseNotification(t,e,i){if(!this.isExpectedSessionStateCurrent(i))return;let s=i.sessionId,r="Response ignored because it was not tied to the current prompt. Please reply to the latest prompt again.",o={prompt_response_rejected:!0,reason:t,eventId:e.eventId,eventPromptId:this.getEventPromptId(e),activePromptId:this.pendingInteractivePrompt?.promptId},a=this.encryptForEmit(r,o,u.EventType.NOTIFICATION,s);if(a)try{await this.appSyncClient.createEvent({sessionId:s,type:u.EventType.NOTIFICATION,source:u.EventSource.DESKTOP,content:a.content,metadata:a.metadata,timestamp:(0,u.prepareEventTimestamp)({orderingKey:s}),...a.isEncrypted?{isEncrypted:!0}:{}})}catch(l){n.warn("Failed to emit rejected prompt response notification",{reason:t,error:l})}}buildToolDetailsForInteractivePrompt(t,e){let i=t.toolName,s=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:c,newStartLine:d}=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:c??h.oldStartLine,new_start_line:d??h.newStartLine}}}}if(this.mapToolNameForApproval(i)==="Bash"){let o=this.firstString(s?.command,s?.cmd,t.rawInput,t.hint);if(o)return{tool_name:"Bash",tool_input:{command:o,output:s?.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}:{}}firstString(...t){for(let e of t)if(typeof e=="string"&&e.trim().length>0)return e}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?{exec_command:"Bash",exec:"Bash",local_shell:"Bash",Bash:"Bash",apply_patch:"Edit",shell_command:"Bash",shell:"Bash",Edit:"Edit",Write:"Write"}[t]||t:void 0}extractOldNewFromPatch(t){let e=[],i=[],s=/^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/,r=0,o=0,a=0,l,c;for(let d of t.split(`
28
+ `)){let h=d.match(s);if(h){r+=1,o=Number.parseInt(h[1],10),a=Number.parseInt(h[2],10);continue}if(!(d.startsWith("***")||d.startsWith("---")||d.startsWith("+++")||d.startsWith("*** End Patch"))){if(d.startsWith("-"))l===void 0&&(l=o),e.push(d.slice(1)),o+=1;else if(d.startsWith("+"))c===void 0&&(c=a),i.push(d.slice(1)),a+=1;else if(d.startsWith(" ")){let g=d.slice(1);e.push(g),i.push(g),o+=1,a+=1}}}return{oldString:e.join(`
23
29
  `),newString:i.join(`
24
- `),oldStartLine:r===1?a:void 0,newStartLine:r===1?p:void 0}}extractDiffLineAnchorsFromSnapshot(e){let t=(0,C.normalizeSnapshot)(e),i,s;for(let r of t.split(`
25
- `)){let o=r.match(/^\s*(\d+)\s+(.*)$/);if(!o)continue;let l=Number.parseInt(o[1],10),a=o[2];if(Number.isFinite(l)){if(a.startsWith("-")){i??=l;continue}if(a.startsWith("+")){s??=l;continue}i??=l,s??=l}}return n.debug("Recovered diff line anchors from tmux snapshot",{oldStartLine:i,newStartLine:s,snapshotPreview:this.summarizePromptSnapshot(e)}),{oldStartLine:i,newStartLine:s}}subscribeToMobileEvents(e){let t=this.sessionState;if(!t||t.sessionId!==e)return n.warn("Refusing mobile subscription without matching session generation",{sessionId:e,currentSessionId:t?.sessionId}),!1;if(this.subscribedSessionId===e&&this.subscribedSessionState===t)return n.info("Already subscribed to mobile events, skipping",{sessionId:e}),!0;if(n.info("Subscribing to mobile events",{sessionId:e}),this.unsubscribe){try{this.unsubscribe()}catch(i){n.warn("Error cleaning up previous subscription (non-fatal)",{error:i})}this.subscribedSessionId=null,this.subscribedSessionState=null}try{this.unsubscribe=this.appSyncClient.subscribeToEvents(e,async i=>{await this.handleMobileEvent(i,t)},i=>{n.error("Subscription error:",i)},{onSessionRetired:()=>this.recoverRetiredBackendSession(e)}),this.subscribedSessionId=e,this.subscribedSessionState=t}catch(i){return n.error("Failed to subscribe to mobile events (non-fatal)",{sessionId:e,error:i}),!1}return this.isExpectedSessionStateCurrent(t)&&(t.subscriptionActive=!0),n.info("Subscribed to mobile events"),!0}async ensureOrchestrationClient(e){let{OrchestrationClient:t}=await import("@quantiya/quorum-core"),i=await d.keychainManager.getTokens((0,d.getEnvironment)());if(!i?.idToken)throw new Error("No Cognito ID token available \u2014 plugin must be logged in");let s=(0,d.getConfig)(),r=this.sessionKey??Buffer.alloc(32).toString("base64"),o=require("ws");return await t.connect({appsyncUrl:s.aws.appsyncUrl,cognitoIdToken:i.idToken,tokenProvider:async()=>(await d.keychainManager.getTokens((0,d.getEnvironment)()))?.idToken??i.idToken,sessionId:e,sessionKeyB64:r,webSocketFactory:((a,p)=>new o(a,p))})}async subscribeToOnApplyUserDecision(e){if(this.userDecisionUnsubscribe){try{this.userDecisionUnsubscribe()}catch(t){n.warn("Error invoking previous onApplyUserDecision unsubscribe (non-fatal)",{error:t})}this.userDecisionUnsubscribe=null}if(this.orchestrationClient){try{await this.orchestrationClient.destroy()}catch(t){n.warn("Error destroying previous OrchestrationClient (non-fatal)",{error:t})}this.orchestrationClient=null}try{let t=await this.ensureOrchestrationClient(e);this.orchestrationClient=t;let i=await t.onApplyUserDecision(s=>{this.handleApplyUserDecisionEvent(s)});return this.userDecisionUnsubscribe=i,n.info("[v1-bridge] Subscribed to onApplyUserDecision via OrchestrationClient",{sessionId:e}),!0}catch(t){if(n.error("Failed to subscribe to onApplyUserDecision (non-fatal)",{sessionId:e,error:t}),this.orchestrationClient){try{await this.orchestrationClient.destroy()}catch{}this.orchestrationClient=null}return!1}}handleApplyUserDecisionEvent(e){let{gateId:t,decision:i,sessionId:s,taskId:r}=e;if(this.v1Bridge.dispatch(e)==="self_echo"){n.debug("[v1-bridge] Self-origin decision echo absorbed",{gateId:t,decision:i,sessionId:s,taskId:r});return}n.info("[v1-bridge] cross-device decision recorded",{gateId:t,decision:i,sessionId:s,taskId:r});let l=String(i).toUpperCase();this.emitOrchestrationBanner(l)}async emitOrchestrationBanner(e){let t=process.env[D];if(!t){n.warn("[v1-bridge] No tmux session set; cannot emit cross-device banner",{expectedEnv:D});return}let s=ke(e).replace(/"/g,'\\"'),r=`tmux display-message -d 8000 -t "${t}" "${s}"`;try{await Ne(r),n.info("[v1-bridge] Cross-device decision banner emitted",{decision:e})}catch(o){n.warn("[v1-bridge] tmux display-message failed (non-fatal)",{error:o instanceof Error?o.message:String(o)})}}async originateUserDecision(e){this.v1Bridge.markOriginated(e.gateId);let t=this.sessionState?.sessionId;if(!t)throw this.v1Bridge.clearOriginated(e.gateId),new Error("No active session \u2014 cannot originate user decision");let i=this.orchestrationClient,s=!1;i||(i=await this.ensureOrchestrationClient(t),s=!0);try{await i.applyUserDecision(e),n.info("[v1-bridge] originated user decision",{gateId:e.gateId,decision:e.decision,sessionId:t})}catch(r){throw this.v1Bridge.clearOriginated(e.gateId),n.error("[v1-bridge] applyUserDecision failed (rolled back originator marker)",{gateId:e.gateId,decision:e.decision,error:r instanceof Error?r.message:String(r)}),r}finally{if(s&&i)try{await i.destroy()}catch{}}}_v1BridgeStateForTest(){return this.v1Bridge}_v1BridgeHandleEventForTest(e){this.handleApplyUserDecisionEvent(e)}async downloadAttachment(e,t,i,s){try{let r=e.isEncrypted??s??!1;n.info("Downloading attachment",{id:e.id,type:e.type,filename:e.filename,s3Key:e.s3Key,attachmentIsEncrypted:e.isEncrypted,eventIsEncrypted:s,shouldDecrypt:r});let{downloadUrl:o}=await this.appSyncClient.getAttachmentDownloadUrl(e.s3Key),l=await fetch(o);if(!l.ok)throw new Error(`Failed to download attachment: ${l.status} ${l.statusText}`);let a=Buffer.from(await l.arrayBuffer());if(r&&i)try{n.info("Decrypting attachment",{id:e.id}),a=d.cryptoService.decryptData(a,i),n.info("Attachment decrypted successfully",{id:e.id,decryptedSize:a.length})}catch(g){throw n.error("Failed to decrypt attachment:",{id:e.id,error:g}),new Error("Failed to decrypt attachment")}else if(r&&!i)return n.warn("Cannot decrypt attachment - no session key available",{id:e.id}),null;let p=N.join(De.tmpdir(),"codevibe-codex",t);A.existsSync(p)||A.mkdirSync(p,{recursive:!0});let u="";if(e.filename){let g=N.extname(e.filename);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 h=`attachment-${e.id}${u}`,m=N.join(p,h);return A.writeFileSync(m,a),n.info("Attachment saved to temp file",{id:e.id,filePath:m,size:a.length}),m}catch(r){return n.error("Failed to download attachment:",{id:e.id,error:r}),null}}async handleMobileEvent(e,t=this.sessionState){if(e.attachments&&e.attachments.length>0&&n.info("DEBUG: Raw attachment data from subscription",{attachments:JSON.stringify(e.attachments),eventIsEncrypted:e.isEncrypted}),n.info("Received mobile event",{eventId:e.eventId,type:e.type,content:e.content?.substring(0,50),attachmentCount:e.attachments?.length||0,isEncrypted:e.isEncrypted}),!t||!this.isExpectedSessionStateCurrent(t)||e.sessionId!==t.sessionId){n.warn("Dropping mobile event for stale or mismatched session generation",{eventSessionId:e.sessionId,expectedSessionId:t?.sessionId,currentSessionId:this.sessionState?.sessionId});return}let i=t.sessionId,{sessionKey:s}=t.encryptionSnapshot,r=e.content||"";if(e.isEncrypted){if(!s){n.error("Dropping encrypted mobile event without its session-pinned key",{eventId:e.eventId,sessionId:i});return}try{r=d.cryptoService.decryptContent(e.content,s),n.debug("Event decrypted successfully",{eventId:e.eventId})}catch(o){n.error("Failed to decrypt event:",{eventId:e.eventId,error:o});return}}try{await this.appSyncClient.updateEventStatus({eventId:e.eventId,sessionId:e.sessionId,timestamp:e.timestamp,deliveryStatus:d.DeliveryStatus.DELIVERED})}catch(o){n.error("Failed to update delivery status:",o)}if(this.isExpectedSessionStateCurrent(t)&&(e.type===d.EventType.USER_PROMPT||e.type===d.EventType.PROMPT_RESPONSE)){let o=r,l=e.attachments||[],a=e.type===d.EventType.PROMPT_RESPONSE;if(e.type===d.EventType.PROMPT_RESPONSE){let m=this.getEventPromptId(e),g=this.pendingInteractivePrompt?.promptId;if(!m||!g||m!==g){n.warn("Rejecting stale or unbound PROMPT_RESPONSE",{eventId:e.eventId,eventPromptId:m,activePromptId:g}),await this.emitRejectedPromptResponseNotification("prompt_id_mismatch",e,t);return}}else this.pendingInteractivePrompt&&this.isApprovalResponseLike(o)&&(a=!0,n.info("Treating approval-shaped USER_PROMPT as active prompt response",{eventId:e.eventId,activePromptId:this.pendingInteractivePrompt.promptId,promptPreview:o.slice(0,20)}));let p=[];if(l.length>0){n.info("Downloading attachments for prompt",{count:l.length});for(let m of l){let g=await this.downloadAttachment(m,i,s,e.isEncrypted);if(!this.isExpectedSessionStateCurrent(t))return;g&&p.push(g)}if(p.length>0){let m=p.map(g=>`[Attached file: ${g}]`).join(`
26
- `);o?o=`${m}
30
+ `),oldStartLine:r===1?l:void 0,newStartLine:r===1?c:void 0}}extractDiffLineAnchorsFromSnapshot(t){let e=(0,M.normalizeSnapshot)(t),i,s;for(let r of e.split(`
31
+ `)){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("+")){s??=a;continue}i??=a,s??=a}}return n.debug("Recovered diff line anchors from tmux snapshot",{oldStartLine:i,newStartLine:s,snapshotPreview:this.summarizePromptSnapshot(t)}),{oldStartLine:i,newStartLine:s}}subscribeToMobileEvents(t){let e=this.sessionState;if(!e||e.sessionId!==t)return n.warn("Refusing mobile subscription without matching session generation",{sessionId:t,currentSessionId:e?.sessionId}),!1;if(this.subscribedSessionId===t&&this.subscribedSessionState===e)return n.info("Already subscribed to mobile events, skipping",{sessionId:t}),!0;if(n.info("Subscribing to mobile events",{sessionId:t}),this.unsubscribe){try{this.unsubscribe()}catch(i){n.warn("Error cleaning up previous subscription (non-fatal)",{error:i})}this.subscribedSessionId=null,this.subscribedSessionState=null}try{this.unsubscribe=this.appSyncClient.subscribeToEvents(t,async i=>{await this.handleMobileEvent(i,e)},i=>{n.error("Subscription error:",i)},{onSessionRetired:()=>this.recoverRetiredBackendSession(t)}),this.subscribedSessionId=t,this.subscribedSessionState=e}catch(i){return n.error("Failed to subscribe to mobile events (non-fatal)",{sessionId:t,error:i}),!1}return this.isExpectedSessionStateCurrent(e)&&(e.subscriptionActive=!0),n.info("Subscribed to mobile events"),!0}async ensureOrchestrationClient(t){let{OrchestrationClient:e}=await import("@quantiya/quorum-core"),i=await u.keychainManager.getTokens((0,u.getEnvironment)());if(!i?.idToken)throw new Error("No Cognito ID token available \u2014 plugin must be logged in");let s=(0,u.getConfig)(),r=this.sessionKey??Buffer.alloc(32).toString("base64"),o=require("ws");return await e.connect({appsyncUrl:s.aws.appsyncUrl,cognitoIdToken:i.idToken,tokenProvider:async()=>(await u.keychainManager.getTokens((0,u.getEnvironment)()))?.idToken??i.idToken,sessionId:t,sessionKeyB64:r,webSocketFactory:((l,c)=>new o(l,c))})}async subscribeToOnApplyUserDecision(t){if(this.userDecisionUnsubscribe){try{this.userDecisionUnsubscribe()}catch(e){n.warn("Error invoking previous onApplyUserDecision unsubscribe (non-fatal)",{error:e})}this.userDecisionUnsubscribe=null}if(this.orchestrationClient){try{await this.orchestrationClient.destroy()}catch(e){n.warn("Error destroying previous OrchestrationClient (non-fatal)",{error:e})}this.orchestrationClient=null}try{let e=await this.ensureOrchestrationClient(t);this.orchestrationClient=e;let i=await e.onApplyUserDecision(s=>{this.handleApplyUserDecisionEvent(s)});return this.userDecisionUnsubscribe=i,n.info("[v1-bridge] Subscribed to onApplyUserDecision via OrchestrationClient",{sessionId:t}),!0}catch(e){if(n.error("Failed to subscribe to onApplyUserDecision (non-fatal)",{sessionId:t,error:e}),this.orchestrationClient){try{await this.orchestrationClient.destroy()}catch{}this.orchestrationClient=null}return!1}}handleApplyUserDecisionEvent(t){let{gateId:e,decision:i,sessionId:s,taskId:r}=t;if(this.v1Bridge.dispatch(t)==="self_echo"){n.debug("[v1-bridge] Self-origin decision echo absorbed",{gateId:e,decision:i,sessionId:s,taskId:r});return}n.info("[v1-bridge] cross-device decision recorded",{gateId:e,decision:i,sessionId:s,taskId:r});let a=String(i).toUpperCase();this.emitOrchestrationBanner(a)}async emitOrchestrationBanner(t){let e=process.env[$];if(!e){n.warn("[v1-bridge] No tmux session set; cannot emit cross-device banner",{expectedEnv:$});return}let s=Xe(t).replace(/"/g,'\\"'),r=`tmux display-message -d 8000 -t "${e}" "${s}"`;try{await Ze(r),n.info("[v1-bridge] Cross-device decision banner emitted",{decision:t})}catch(o){n.warn("[v1-bridge] tmux display-message failed (non-fatal)",{error:o instanceof Error?o.message:String(o)})}}async originateUserDecision(t){this.v1Bridge.markOriginated(t.gateId);let e=this.sessionState?.sessionId;if(!e)throw this.v1Bridge.clearOriginated(t.gateId),new Error("No active session \u2014 cannot originate user decision");let i=this.orchestrationClient,s=!1;i||(i=await this.ensureOrchestrationClient(e),s=!0);try{await i.applyUserDecision(t),n.info("[v1-bridge] originated user decision",{gateId:t.gateId,decision:t.decision,sessionId:e})}catch(r){throw this.v1Bridge.clearOriginated(t.gateId),n.error("[v1-bridge] applyUserDecision failed (rolled back originator marker)",{gateId:t.gateId,decision:t.decision,error:r instanceof Error?r.message:String(r)}),r}finally{if(s&&i)try{await i.destroy()}catch{}}}_v1BridgeStateForTest(){return this.v1Bridge}_v1BridgeHandleEventForTest(t){this.handleApplyUserDecisionEvent(t)}async downloadAttachment(t,e,i,s){try{let r=t.isEncrypted??s??!1;n.info("Downloading attachment",{id:t.id,type:t.type,filename:t.filename,s3Key:t.s3Key,attachmentIsEncrypted:t.isEncrypted,eventIsEncrypted:s,shouldDecrypt:r});let{downloadUrl:o}=await this.appSyncClient.getAttachmentDownloadUrl(t.s3Key),a=await fetch(o);if(!a.ok)throw new Error(`Failed to download attachment: ${a.status} ${a.statusText}`);let l=Buffer.from(await a.arrayBuffer());if(r&&i)try{n.info("Decrypting attachment",{id:t.id}),l=u.cryptoService.decryptData(l,i),n.info("Attachment decrypted successfully",{id:t.id,decryptedSize:l.length})}catch(v){throw n.error("Failed to decrypt attachment:",{id:t.id,error:v}),new Error("Failed to decrypt attachment")}else if(r&&!i)return n.warn("Cannot decrypt attachment - no session key available",{id:t.id}),null;let c=z.join(Qe.tmpdir(),"codevibe-codex",e);D.existsSync(c)||D.mkdirSync(c,{recursive:!0});let d="";if(t.filename){let v=z.extname(t.filename);v&&(d=v)}d||(d={"image/jpeg":".jpg","image/png":".png","image/gif":".gif","image/webp":".webp","image/heic":".heic","application/pdf":".pdf"}[t.type]||".bin");let h=`attachment-${t.id}${d}`,g=z.join(c,h);return D.writeFileSync(g,l),n.info("Attachment saved to temp file",{id:t.id,filePath:g,size:l.length}),g}catch(r){return n.error("Failed to download attachment:",{id:t.id,error:r}),null}}async handleMobileEvent(t,e=this.sessionState){if(t.attachments&&t.attachments.length>0&&n.info("DEBUG: Raw attachment data from subscription",{attachments:JSON.stringify(t.attachments),eventIsEncrypted:t.isEncrypted}),n.info("Received mobile event",{eventId:t.eventId,type:t.type,content:t.content?.substring(0,50),attachmentCount:t.attachments?.length||0,isEncrypted:t.isEncrypted}),!e||!this.isExpectedSessionStateCurrent(e)||t.sessionId!==e.sessionId){n.warn("Dropping mobile event for stale or mismatched session generation",{eventSessionId:t.sessionId,expectedSessionId:e?.sessionId,currentSessionId:this.sessionState?.sessionId});return}let i=e.sessionId,{sessionKey:s}=e.encryptionSnapshot,r=t.content||"";if(t.isEncrypted){if(!s){n.error("Dropping encrypted mobile event without its session-pinned key",{eventId:t.eventId,sessionId:i});return}try{r=u.cryptoService.decryptContent(t.content,s),n.debug("Event decrypted successfully",{eventId:t.eventId})}catch(o){n.error("Failed to decrypt event:",{eventId:t.eventId,error:o});return}}try{await this.appSyncClient.updateEventStatus({eventId:t.eventId,sessionId:t.sessionId,timestamp:t.timestamp,deliveryStatus:u.DeliveryStatus.DELIVERED})}catch(o){n.error("Failed to update delivery status:",o)}if(this.isExpectedSessionStateCurrent(e)&&(t.type===u.EventType.USER_PROMPT||t.type===u.EventType.PROMPT_RESPONSE)){let o=r,a=t.attachments||[],l=t.type===u.EventType.PROMPT_RESPONSE;if(t.type===u.EventType.PROMPT_RESPONSE){let g=this.getEventPromptId(t),v=this.pendingInteractivePrompt?.promptId;if(!g||!v||g!==v){n.warn("Rejecting stale or unbound PROMPT_RESPONSE",{eventId:t.eventId,eventPromptId:g,activePromptId:v}),await this.emitRejectedPromptResponseNotification("prompt_id_mismatch",t,e);return}}else this.pendingInteractivePrompt&&this.isApprovalResponseLike(o)&&(l=!0,n.info("Treating approval-shaped USER_PROMPT as active prompt response",{eventId:t.eventId,activePromptId:this.pendingInteractivePrompt.promptId,promptPreview:o.slice(0,20)}));let c=[];if(a.length>0){n.info("Downloading attachments for prompt",{count:a.length});for(let g of a){let v=await this.downloadAttachment(g,i,s,t.isEncrypted);if(!this.isExpectedSessionStateCurrent(e))return;v&&c.push(v)}if(c.length>0){let g=c.map(v=>`[Attached file: ${v}]`).join(`
32
+ `);o?o=`${g}
27
33
 
28
- ${o}`:o=`${m}
34
+ ${o}`:o=`${g}
29
35
 
30
- Please analyze the attached file(s).`,n.info("Prompt updated with attachment paths",{attachmentCount:p.length,newPromptLength:o.length})}}let u=this.translatePromptResponse(o);if(!this.isExpectedSessionStateCurrent(t)||(u.primaryInput!==F&&this.trackMobilePrompt(i,u.primaryInput),!this.isExpectedSessionStateCurrent(t)))return;let h=await this.sendSessionPinnedInput(t,u.primaryInput);if(!this.isExpectedSessionStateCurrent(t))return;if(!h&&u.primaryInput!==F&&this.forgetMobilePrompt(i,u.primaryInput),h&&u.followUpInput){if(this.trackMobilePrompt(i,u.followUpInput),!this.isExpectedSessionStateCurrent(t))return;let m=await this.sendSessionPinnedInput(t,u.followUpInput);if(!this.isExpectedSessionStateCurrent(t))return;m||this.forgetMobilePrompt(i,u.followUpInput)}if(h&&this.pendingInteractivePrompt&&a){let m=this.pendingInteractivePrompt;if(this.rememberResolvedApprovalDedupeKey(t.sessionId,m.dedupeKey),this.pendingInteractivePrompt=null,await this.emitNextQueuedInteractivePrompt(),!this.isExpectedSessionStateCurrent(t))return}if(h){if(!this.isExpectedSessionStateCurrent(t))return;try{await this.appSyncClient.updateEventStatus({eventId:e.eventId,sessionId:e.sessionId,timestamp:e.timestamp,deliveryStatus:d.DeliveryStatus.EXECUTED})}catch(m){n.error("Failed to update executed status:",m)}}}}buildToolActivityIntegration(){return new k({logger:n,createEvent:e=>this.appSyncClient.createEvent(e),encryptContent:(e,t)=>d.cryptoService.encryptContent(e,t),encryptMetadata:(e,t)=>d.cryptoService.encryptMetadata(e,t),getSessionKey:()=>this.sessionKey,getSessionKeyGen:()=>this.sessionKeyGen,refreshSessionKey:e=>this.refreshToolActivitySessionKey(e)})}async refreshToolActivitySessionKey(e){try{let t=await this.appSyncClient.getSession(e);if(!t||!t.isEncrypted)return null;let i=t.sessionKeyGen??null,s=t.encryptedKeys??[];if(s.length===0)return null;let r=await d.keychainManager.getSessionKey(e,s,i);return r?(n.info("[tool-activity] refreshed session key for SessionKeyStale retry (live key untouched)",{sessionId:e,sessionKeyGen:i}),{sessionKey:r,sessionKeyGen:i}):null}catch(t){return n.warn("[tool-activity] session key refresh failed after SessionKeyStale (staying retryable)",{sessionId:e,error:String(t)}),null}}getToolActivityLegacyRecorder(e){let t=this.toolActivityLegacyRecorders.get(e);return t||(t=new d.ToolActivityLedger({agent:"codex",sessionId:e,logger:n}),this.toolActivityLegacyRecorders.set(e,t)),t}async ensureToolActivityStarted(e){if(!this.sessionState||!e)return;let t=this.sessionWatcher.getActiveLogFile()||this.sessionState.codexLogFile||void 0,i=this.toolActivity;if(i){t&&i.bindTranscript(t);return}if(this.toolActivityStartPromise){await this.toolActivityStartPromise,t&&this.toolActivity?.bindTranscript(t);return}if(!this.sessionKey)return;let s=this.sessionState.sessionId;this.toolActivityStartPromise=(async()=>{let r=this.buildToolActivityIntegration();try{await r.start(s,e,t),this.toolActivity=r}catch(o){throw await r.stop().catch(()=>{}),this.toolActivityStartPromise=null,o}})().catch(r=>{n.warn("[tool-activity] failed to start codex consolidator (non-fatal)",{backendSessionId:s,rolloutId:e,error:String(r)})}),await this.toolActivityStartPromise}startMobileEndWatcher(e){!this.sessionState||this.sessionState.sessionId!==e||(this.sessionState.mobileEndWatcher=this.appSyncClient.watchForMobileEnd(e,async()=>{n.info("Mobile ended session \u2014 sending desktop quit",{sessionId:e}),this.appSyncClient.stopHeartbeat(e),this.appSyncClient.cleanupSubscription(e);try{await this.appSyncClient.updateSession({sessionId:e,status:d.SessionStatus.INACTIVE}),n.info("Marked session INACTIVE on mobile-end (before tmux terminate)",{sessionId:e})}catch(i){n.warn("Failed to mark session INACTIVE on mobile-end",{sessionId:e,error:i})}let t=process.env[D];if(!t){n.warn("No tmux session set; skipping desktop self-terminate",{sessionId:e,expectedEnv:D});return}await Pt(t,gt)}))}async endActiveSession(e){if(!this.sessionState)return;let t=this.sessionState;if(this.terminalInputBlockedStates.add(t),await this.terminalInputChain.catch(()=>{}),this.sessionState!==t){n.warn("Session changed while waiting for terminal-input teardown barrier",{endingSessionId:t.sessionId,currentSessionId:this.sessionState?.sessionId,reason:e});return}if(n.info("Ending active session",{sessionId:t.sessionId,codexSessionId:t.codexSessionId,reason:e}),this.sessionState.mobileEndWatcher&&(this.sessionState.mobileEndWatcher.stop(),this.sessionState.mobileEndWatcher=void 0),this.userDecisionUnsubscribe){try{this.userDecisionUnsubscribe()}catch(i){n.warn("Error invoking onApplyUserDecision unsubscribe (non-fatal)",{error:i})}this.userDecisionUnsubscribe=null}if(this.orchestrationClient){try{await this.orchestrationClient.destroy()}catch(i){n.warn("Error destroying OrchestrationClient (non-fatal)",{error:i})}this.orchestrationClient=null}this.v1Bridge.clear(),this.appSyncClient.stopHeartbeat(this.sessionState.sessionId),this.stopE1TtlRefresh(),this.unsubscribe&&(this.unsubscribe(),this.unsubscribe=null,this.subscribedSessionId=null,this.subscribedSessionState=null),await this.tmuxPaneObserver.stop(),this.pendingInteractivePrompt=null,this.queuedInteractivePrompts=[],this.isInitializingSession=!1,this.bufferedLogEntries=[],e==="shutdown"&&this.sessionWatcher.stop(),this.toolActivityStartPromise&&(await this.toolActivityStartPromise.catch(()=>{}),this.toolActivityStartPromise=null),await this.logEntryChain.catch(()=>{}),this.toolActivity&&(await this.toolActivity.stop(),this.toolActivity=null);for(let i of this.toolActivityLegacyRecorders.values())i.disposeLegacyCoordination();this.toolActivityLegacyRecorders.clear(),this.sessionKey&&(d.keychainManager.clearSessionKey(this.sessionState.sessionId),this.sessionKey=null,this.sessionKeyGen=null);try{await this.appSyncClient.updateSession({sessionId:this.sessionState.sessionId,status:d.SessionStatus.INACTIVE})}catch(i){n.error("Failed to update session status:",i)}this.sessionState=null}async stop(){n.info("Stopping CodeVibe Codex companion server"),this.stopTmuxLifecycleMonitor(),this.isStopping=!0,this.lifecycleGeneration+=1;let e=this.launchSessionInitPromise,t=this.sessionStartedInitPromise,i=this.retiredSessionRecoveryPromise;await Promise.all([e?.catch(()=>{}),t?.catch(()=>{}),i?.catch(()=>{})]);try{await this.endActiveSession("shutdown"),this.sessionWatcher.stop(),this.approvalDetector.shutdown(),he()}finally{await this.httpApi.stop(),this.appSyncClient.cleanupSubscriptions()}n.info("CodeVibe Codex companion server stopped")}};if(require.main===module){let c=new V;process.on("SIGINT",async()=>{n.info("Received SIGINT, shutting down..."),await c.stop(),process.exit(0)}),process.on("SIGTERM",async()=>{n.info("Received SIGTERM, shutting down..."),await c.stop(),process.exit(0)}),(0,d.installDaemonProcessGuards)(n,{onFatal:()=>{c.stop().finally(()=>process.exit(1))}}),c.start().catch(e=>{n.error("Failed to start server:",e),process.exit(1)})}0&&(module.exports={CodexCompanionServer,extractShellCommandFromPromptText,extractShellCommandPartsFromPromptText});
36
+ Please analyze the attached file(s).`,n.info("Prompt updated with attachment paths",{attachmentCount:c.length,newPromptLength:o.length})}}let d=this.translatePromptResponse(o);if(!this.isExpectedSessionStateCurrent(e)||(d.primaryInput!==K&&this.trackMobilePrompt(i,d.primaryInput),!this.isExpectedSessionStateCurrent(e)))return;let h=await this.sendSessionPinnedInput(e,d.primaryInput);if(!this.isExpectedSessionStateCurrent(e))return;if(!h&&d.primaryInput!==K&&this.forgetMobilePrompt(i,d.primaryInput),h&&d.followUpInput){if(this.trackMobilePrompt(i,d.followUpInput),!this.isExpectedSessionStateCurrent(e))return;let g=await this.sendSessionPinnedInput(e,d.followUpInput);if(!this.isExpectedSessionStateCurrent(e))return;g||this.forgetMobilePrompt(i,d.followUpInput)}if(h&&this.pendingInteractivePrompt&&l){let g=this.pendingInteractivePrompt;if(this.rememberResolvedApprovalDedupeKey(e.sessionId,g.dedupeKey),this.pendingInteractivePrompt=null,await this.emitNextQueuedInteractivePrompt(),!this.isExpectedSessionStateCurrent(e))return}if(h){if(!this.isExpectedSessionStateCurrent(e))return;try{await this.appSyncClient.updateEventStatus({eventId:t.eventId,sessionId:t.sessionId,timestamp:t.timestamp,deliveryStatus:u.DeliveryStatus.EXECUTED})}catch(g){n.error("Failed to update executed status:",g)}}}}buildToolActivityIntegration(){return new U({logger:n,createEvent:t=>this.appSyncClient.createEvent(t),encryptContent:(t,e)=>u.cryptoService.encryptContent(t,e),encryptMetadata:(t,e)=>u.cryptoService.encryptMetadata(t,e),getSessionKey:()=>this.sessionKey,getSessionKeyGen:()=>this.sessionKeyGen,refreshSessionKey:t=>this.refreshToolActivitySessionKey(t)})}async refreshToolActivitySessionKey(t){try{let e=await this.appSyncClient.getSession(t);if(!e||!e.isEncrypted)return null;let i=e.sessionKeyGen??null,s=e.encryptedKeys??[];if(s.length===0)return null;let r=await u.keychainManager.getSessionKey(t,s,i);return r?(n.info("[tool-activity] refreshed session key for SessionKeyStale retry (live key untouched)",{sessionId:t,sessionKeyGen:i}),{sessionKey:r,sessionKeyGen:i}):null}catch(e){return n.warn("[tool-activity] session key refresh failed after SessionKeyStale (staying retryable)",{sessionId:t,error:String(e)}),null}}getToolActivityLegacyRecorder(t){let e=this.toolActivityLegacyRecorders.get(t);return e||(e=new u.ToolActivityLedger({agent:"codex",sessionId:t,logger:n}),this.toolActivityLegacyRecorders.set(t,e)),e}async ensureToolActivityStarted(t){if(!this.sessionState||!t)return;let e=this.sessionWatcher.getActiveLogFile()||this.sessionState.codexLogFile||void 0,i=this.toolActivity;if(i){e&&i.bindTranscript(e);return}if(this.toolActivityStartPromise){await this.toolActivityStartPromise,e&&this.toolActivity?.bindTranscript(e);return}if(!this.sessionKey)return;let s=this.sessionState.sessionId;this.toolActivityStartPromise=(async()=>{let r=this.buildToolActivityIntegration();try{await r.start(s,t,e),this.toolActivity=r}catch(o){throw await r.stop().catch(()=>{}),this.toolActivityStartPromise=null,o}})().catch(r=>{n.warn("[tool-activity] failed to start codex consolidator (non-fatal)",{backendSessionId:s,rolloutId:t,error:String(r)})}),await this.toolActivityStartPromise}startMobileEndWatcher(t){!this.sessionState||this.sessionState.sessionId!==t||(this.sessionState.mobileEndWatcher=this.appSyncClient.watchForMobileEnd(t,async()=>{n.info("Mobile ended session \u2014 sending desktop quit",{sessionId:t}),this.appSyncClient.stopHeartbeat(t),this.appSyncClient.cleanupSubscription(t);try{await this.appSyncClient.updateSession({sessionId:t,status:u.SessionStatus.INACTIVE}),n.info("Marked session INACTIVE on mobile-end (before tmux terminate)",{sessionId:t})}catch(i){n.warn("Failed to mark session INACTIVE on mobile-end",{sessionId:t,error:i})}let e=process.env[$];if(!e){n.warn("No tmux session set; skipping desktop self-terminate",{sessionId:t,expectedEnv:$});return}await Yt(e,Ht)}))}async endActiveSession(t){if(!this.sessionState)return;let e=this.sessionState;if(this.terminalInputBlockedStates.add(e),await this.terminalInputChain.catch(()=>{}),this.sessionState!==e){n.warn("Session changed while waiting for terminal-input teardown barrier",{endingSessionId:e.sessionId,currentSessionId:this.sessionState?.sessionId,reason:t});return}if(n.info("Ending active session",{sessionId:e.sessionId,codexSessionId:e.codexSessionId,reason:t}),this.sessionState.mobileEndWatcher&&(this.sessionState.mobileEndWatcher.stop(),this.sessionState.mobileEndWatcher=void 0),this.userDecisionUnsubscribe){try{this.userDecisionUnsubscribe()}catch(i){n.warn("Error invoking onApplyUserDecision unsubscribe (non-fatal)",{error:i})}this.userDecisionUnsubscribe=null}if(this.orchestrationClient){try{await this.orchestrationClient.destroy()}catch(i){n.warn("Error destroying OrchestrationClient (non-fatal)",{error:i})}this.orchestrationClient=null}this.v1Bridge.clear(),this.appSyncClient.stopHeartbeat(this.sessionState.sessionId),this.stopE1TtlRefresh(),this.unsubscribe&&(this.unsubscribe(),this.unsubscribe=null,this.subscribedSessionId=null,this.subscribedSessionState=null),await this.tmuxPaneObserver.stop(),this.pendingInteractivePrompt=null,this.queuedInteractivePrompts=[],this.isInitializingSession=!1,this.bufferedLogEntries=[],ue(),t==="shutdown"&&this.sessionWatcher.stop(),this.toolActivityStartPromise&&(await this.toolActivityStartPromise.catch(()=>{}),this.toolActivityStartPromise=null),await this.logEntryChain.catch(()=>{}),this.toolActivity&&(await this.toolActivity.stop(),this.toolActivity=null);for(let i of this.toolActivityLegacyRecorders.values())i.disposeLegacyCoordination();this.toolActivityLegacyRecorders.clear(),this.sessionKey&&(u.keychainManager.clearSessionKey(this.sessionState.sessionId),this.sessionKey=null,this.sessionKeyGen=null);try{await this.appSyncClient.updateSession({sessionId:this.sessionState.sessionId,status:u.SessionStatus.INACTIVE})}catch(i){n.error("Failed to update session status:",i)}this.sessionState=null}async stop(){n.info("Stopping CodeVibe Codex companion server"),this.stopTmuxLifecycleMonitor(),this.isStopping=!0,this.lifecycleGeneration+=1,this.sessionWatcher.stop();let t=this.launchSessionInitPromise,e=this.sessionStartedInitPromise,i=this.retiredSessionRecoveryPromise,s=this.hookEventChain,r=typeof this.sessionWatcher.waitForTranscriptAdmissions=="function"?this.sessionWatcher.waitForTranscriptAdmissions():Promise.resolve();await Promise.all([t?.catch(()=>{}),e?.catch(()=>{}),i?.catch(()=>{}),r.catch(()=>{}),s.catch(()=>{})]);try{await this.endActiveSession("shutdown"),this.sessionWatcher.stop(),this.approvalDetector.shutdown(),ue()}finally{await this.httpApi.stop(),this.appSyncClient.cleanupSubscriptions()}n.info("CodeVibe Codex companion server stopped")}};if(require.main===module){let p=new se;process.on("SIGINT",async()=>{n.info("Received SIGINT, shutting down..."),await p.stop(),process.exit(0)}),process.on("SIGTERM",async()=>{n.info("Received SIGTERM, shutting down..."),await p.stop(),process.exit(0)}),(0,u.installDaemonProcessGuards)(n,{onFatal:()=>{p.stop().finally(()=>process.exit(1))}}),p.start().catch(t=>{n.error("Failed to start server:",t),process.exit(1)})}0&&(module.exports={CodexCompanionServer,extractShellCommandFromPromptText,extractShellCommandPartsFromPromptText});
package/hooks/common.sh CHANGED
@@ -5,13 +5,12 @@
5
5
 
6
6
  # ─── Reviewer-subprocess short-circuit ───────────────────────────────
7
7
  #
8
- # When $QUORUM_REVIEWER_SUBPROCESS is set, the current `codex`
9
- # invocation is a reviewer subprocess spawned by Quorum 2.0's
10
- # `CodexReviewerProvider` (codevibe-core-rs/crates/codevibe-reviewer/
11
- # src/providers/codex.rs). Reviewer subprocesses use
12
- # `codex exec --sandbox read-only --ephemeral` and have their own
13
- # ephemeral session id that MUST NOT interact with the user's
14
- # primary Codex session state.
8
+ # When $QUORUM_REVIEWER_SUBPROCESS is set, the current `codex` invocation is an
9
+ # internal CodeVibe subprocess and MUST NOT interact with the user's primary
10
+ # Codex session state. This isolation marker is dominant: an inherited
11
+ # $CODEVIBE_CODEX_PRIMARY_COMPANION value must never bypass it. The private
12
+ # launcher removes QUORUM only at the exact primary Codex handoff; reviewers and
13
+ # implementors add/inherit QUORUM again and therefore always stop here.
15
14
  #
16
15
  # Without this guard, every reviewer spawn would fire SessionStart →
17
16
  # the plugin's resume/create logic creates a ghost backend session →
@@ -23,16 +22,14 @@
23
22
  # prevents the same bug class for Codex.
24
23
  #
25
24
  # `common.sh` is sourced by every hook script (session-start.sh,
26
- # user-prompt.sh, pre-tool-use.sh, post-tool-use.sh, stop.sh), so
27
- # `exit 0` here propagates to the hook script Codex CLI sees a
28
- # clean hook success and continues normally. Zero-impact for normal
29
- # user sessions (the env var is never set in the 1.0 code path).
25
+ # user-prompt.sh, pre-tool-use.sh, post-tool-use.sh, stop.sh), so `exit 0` here
26
+ # propagates to the hook script and Codex sees a clean hook success.
30
27
  #
31
28
  # This change belongs on 1.0 main because it protects the 1.0
32
29
  # primary-session-isolation invariant. The env var name is the same
33
30
  # `QUORUM_*` scope used by the Claude and Gemini plugins' matching
34
31
  # guards.
35
- if [ -n "$QUORUM_REVIEWER_SUBPROCESS" ]; then
32
+ if [ -n "${QUORUM_REVIEWER_SUBPROCESS:-}" ]; then
36
33
  exit 0
37
34
  fi
38
35
 
@@ -56,7 +53,7 @@ fi
56
53
  #
57
54
  # The right behavior for an unsupported environment is a silent no-op.
58
55
  # `exit 0` propagates a clean hook success to Codex.
59
- if [ -z "$CODEVIBE_CODEX_TMUX_SESSION" ]; then
56
+ if [ -z "${CODEVIBE_CODEX_TMUX_SESSION:-}" ]; then
60
57
  exit 0
61
58
  fi
62
59
 
@@ -139,7 +139,7 @@ case "${1:-}" in
139
139
  echo "Install Codex CLI with: npm install -g @openai/codex" >&2
140
140
  exit 127
141
141
  fi
142
- exec codex "$@"
142
+ exec env -u QUORUM_REVIEWER_SUBPROCESS codex "$@"
143
143
  ;;
144
144
  esac
145
145
 
@@ -379,7 +379,7 @@ if [ -n "$TMUX" ]; then
379
379
  # leaves _CV_RC at its 0 default. printf's `|| true` keeps a
380
380
  # disk-full failure from clobbering diagnostics.
381
381
  _CV_RC=0
382
- codex "$@" || _CV_RC=$?
382
+ env -u QUORUM_REVIEWER_SUBPROCESS codex "$@" || _CV_RC=$?
383
383
  printf '%s' "$_CV_RC" > "$_CV_CODEX_EXIT_FILE" 2>/dev/null || true
384
384
  exit "$_CV_RC"
385
385
  fi
@@ -390,7 +390,7 @@ if [ ! -t 0 ] || [ ! -t 1 ]; then
390
390
  _CV_AGENT_INVOKED="true"
391
391
  _CV_AGENT_STARTED_AT="$(date +%s)"
392
392
  _CV_RC=0
393
- codex "$@" || _CV_RC=$?
393
+ env -u QUORUM_REVIEWER_SUBPROCESS codex "$@" || _CV_RC=$?
394
394
  printf '%s' "$_CV_RC" > "$_CV_CODEX_EXIT_FILE" 2>/dev/null || true
395
395
  exit "$_CV_RC"
396
396
  fi
@@ -399,6 +399,7 @@ fi
399
399
  log "Starting session log watcher server..."
400
400
  export CODEX_WORKING_DIRECTORY="$WORKING_DIR"
401
401
  export CODEVIBE_CODEX_TMUX_SESSION="$SESSION_NAME"
402
+ export CODEVIBE_CODEX_PRIMARY_COMPANION=1
402
403
  export CODEVIBE_CODEX_PLUGIN_DIR="$PLUGIN_DIR"
403
404
 
404
405
  # Install hooks.json for Codex CLI with absolute paths (idempotent)
@@ -523,11 +524,12 @@ _CV_HOOKS_REASON=""
523
524
  #
524
525
  # Design notes:
525
526
  #
526
- # - "already installed" detection: a structured walk of the JSON
527
- # looking for our specific hook-script filenames at expected event
528
- # keys. Replaces the prior `grep -q "codevibe-codex"` substring
529
- # check, which false-positived on unrelated user paths containing
530
- # that string.
527
+ # - "already installed" detection: a structured comparison of the exact
528
+ # current hook commands at every expected event key. An older install path
529
+ # is owned but stale, so it is replaced instead of silently pinning hooks to
530
+ # a prior checkout/package version. This also replaces the former
531
+ # `grep -q "codevibe-codex"` substring test, which false-positived on
532
+ # unrelated user paths containing that string.
531
533
  #
532
534
  # - Merge dedupe: filters our previously-installed entries out of
533
535
  # each event key's array, then appends the fresh template entries.
@@ -586,12 +588,13 @@ if INSTALLER_OUTPUT=$(CV_EXISTING="$CODEX_HOOKS_FILE" \
586
588
  if (filtered.length === 0) return null;
587
589
  return { ...entry, hooks: filtered };
588
590
  };
589
- // Does this matcher entry contain at least one of our hooks anywhere
590
- // in its inner hooks[]? Used by the "already installed" walk only —
591
- // it does NOT decide whether the entry survives a strip.
592
- const entryContainsOurs = (entry) =>
593
- entry && typeof entry === "object" && Array.isArray(entry.hooks) &&
594
- entry.hooks.some((h) => h && isOurCommand(h.command));
591
+ const ownedCommands = (entries) => entries
592
+ .flatMap((entry) => entry && typeof entry === "object" && Array.isArray(entry.hooks)
593
+ ? entry.hooks
594
+ : [])
595
+ .map((hook) => hook && hook.command)
596
+ .filter(isOurCommand)
597
+ .sort();
595
598
 
596
599
  let existingObj = null;
597
600
  let existed = false;
@@ -618,8 +621,9 @@ if INSTALLER_OUTPUT=$(CV_EXISTING="$CODEX_HOOKS_FILE" \
618
621
  nextObj.hooks && typeof nextObj.hooks === "object") ? nextObj.hooks : {};
619
622
  let allPresent = existed;
620
623
  for (const k of Object.keys(nextHooks)) {
621
- const arr = Array.isArray(existingHooks[k]) ? existingHooks[k] : [];
622
- if (!arr.some(entryContainsOurs)) {
624
+ const existingArr = Array.isArray(existingHooks[k]) ? existingHooks[k] : [];
625
+ const nextArr = Array.isArray(nextHooks[k]) ? nextHooks[k] : [];
626
+ if (JSON.stringify(ownedCommands(existingArr)) !== JSON.stringify(ownedCommands(nextArr))) {
623
627
  allPresent = false;
624
628
  break;
625
629
  }
@@ -752,7 +756,7 @@ done
752
756
  # wrapper's cleanup trap can report it via `wrapper_exited` telemetry —
753
757
  # tmux's own attach exit code is independent of the inner process exit.
754
758
  tmux new-session -d -s "$SESSION_NAME" -x "$(tput cols)" -y "$(tput lines)" \
755
- "export CODEVIBE_CODEX_TMUX_SESSION='$SESSION_NAME'; export ENVIRONMENT='$ENVIRONMENT'; $CODEX_CMD; printf '%s' \"\$?\" > '$_CV_CODEX_EXIT_FILE'; exit"
759
+ "unset QUORUM_REVIEWER_SUBPROCESS; export CODEVIBE_CODEX_TMUX_SESSION='$SESSION_NAME'; export CODEVIBE_CODEX_PRIMARY_COMPANION=1; export ENVIRONMENT='$ENVIRONMENT'; $CODEX_CMD; printf '%s' \"\$?\" > '$_CV_CODEX_EXIT_FILE'; exit"
756
760
  _CV_TMUX_STARTED="true"
757
761
  _CV_AGENT_INVOKED="true"
758
762
  _CV_AGENT_STARTED_AT="$(date +%s)"
@@ -0,0 +1,127 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ // tmux pipe-pane must execute a command string in the tmux server. Keep the
5
+ // file-open security and hard byte bound in this tiny helper, independently of
6
+ // the observer's tmux control path. The observer owns both files and passes
7
+ // their expected identities; this process refuses path substitution, reports
8
+ // ready/done through the verified state descriptor, and never grows pane.log
9
+ // past maxBytes even if `tmux pipe-pane -t` later fails.
10
+ const fs = require('fs');
11
+
12
+ const [
13
+ , ,
14
+ filePath,
15
+ expectedDevText,
16
+ expectedInoText,
17
+ statePath,
18
+ expectedStateDevText,
19
+ expectedStateInoText,
20
+ token,
21
+ maxBytesText,
22
+ ] = process.argv;
23
+ const expectedDev = Number(expectedDevText);
24
+ const expectedIno = Number(expectedInoText);
25
+ const expectedStateDev = Number(expectedStateDevText);
26
+ const expectedStateIno = Number(expectedStateInoText);
27
+ const maxBytes = Number(maxBytesText);
28
+
29
+ if (
30
+ !filePath
31
+ || !statePath
32
+ || !/^[a-f0-9]{32}$/.test(token || '')
33
+ || !Number.isSafeInteger(expectedDev)
34
+ || !Number.isSafeInteger(expectedIno)
35
+ || !Number.isSafeInteger(expectedStateDev)
36
+ || !Number.isSafeInteger(expectedStateIno)
37
+ || !Number.isSafeInteger(maxBytes)
38
+ || maxBytes <= 0
39
+ ) {
40
+ process.exit(64);
41
+ }
42
+
43
+ const openVerified = (target, flags, expectedDevice, expectedInode) => {
44
+ const fd = fs.openSync(target, flags | (fs.constants.O_NOFOLLOW || 0));
45
+ const stat = fs.fstatSync(fd);
46
+ if (!stat.isFile() || stat.dev !== expectedDevice || stat.ino !== expectedInode) {
47
+ fs.closeSync(fd);
48
+ throw new Error('identity mismatch');
49
+ }
50
+ return fd;
51
+ };
52
+
53
+ let paneFd;
54
+ let stateFd;
55
+ try {
56
+ paneFd = openVerified(
57
+ filePath,
58
+ fs.constants.O_WRONLY | fs.constants.O_APPEND,
59
+ expectedDev,
60
+ expectedIno,
61
+ );
62
+ stateFd = openVerified(
63
+ statePath,
64
+ fs.constants.O_WRONLY,
65
+ expectedStateDev,
66
+ expectedStateIno,
67
+ );
68
+ } catch {
69
+ if (paneFd !== undefined) try { fs.closeSync(paneFd); } catch { /* ignore */ }
70
+ if (stateFd !== undefined) try { fs.closeSync(stateFd); } catch { /* ignore */ }
71
+ process.exit(66);
72
+ }
73
+
74
+ const writeState = (state, code = '') => {
75
+ const value = Buffer.from(`${state} ${token} ${process.pid}${code === '' ? '' : ` ${code}`}\n`, 'utf8');
76
+ fs.ftruncateSync(stateFd, 0);
77
+ let written = 0;
78
+ while (written < value.length) {
79
+ const count = fs.writeSync(stateFd, value, written, value.length - written, written);
80
+ if (count <= 0) throw new Error('short writer-state write');
81
+ written += count;
82
+ }
83
+ fs.fsyncSync(stateFd);
84
+ };
85
+
86
+ let finished = false;
87
+ const finish = (code) => {
88
+ if (finished) return;
89
+ finished = true;
90
+ process.stdin.pause();
91
+ try { fs.closeSync(paneFd); } catch { /* ignore */ }
92
+ try { writeState('done', code); } catch { /* observer will retain the bounded mirror */ }
93
+ try { fs.closeSync(stateFd); } catch { /* ignore */ }
94
+ process.exit(code);
95
+ };
96
+
97
+ try {
98
+ writeState('ready');
99
+ if (fs.fstatSync(paneFd).size >= maxBytes) finish(0);
100
+ } catch {
101
+ finish(67);
102
+ }
103
+
104
+ process.stdin.on('data', (chunk) => {
105
+ if (finished) return;
106
+ try {
107
+ const size = fs.fstatSync(paneFd).size;
108
+ const remaining = Math.max(0, maxBytes - size);
109
+ const input = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
110
+ const length = Math.min(remaining, input.length);
111
+ let written = 0;
112
+ while (written < length) {
113
+ const count = fs.writeSync(paneFd, input, written, length - written);
114
+ if (count <= 0) throw new Error('short pane-mirror write');
115
+ written += count;
116
+ }
117
+ if (length < input.length || size + written >= maxBytes) finish(0);
118
+ } catch {
119
+ finish(67);
120
+ }
121
+ });
122
+ process.stdin.on('end', () => finish(0));
123
+ process.stdin.on('close', () => finish(0));
124
+ process.stdin.on('error', () => finish(68));
125
+ process.on('SIGHUP', () => finish(0));
126
+ process.on('SIGINT', () => finish(0));
127
+ process.on('SIGTERM', () => finish(0));
@@ -331,8 +331,8 @@ ifeq ($(strip $(foreach prefix,$(NO_LOAD),\
331
331
  endif
332
332
 
333
333
  quiet_cmd_regen_makefile = ACTION Regenerating $@
334
- cmd_regen_makefile = cd $(srcdir); /opt/homebrew/lib/node_modules/npm/node_modules/node-gyp/gyp/gyp_main.py -fmake --ignore-environment "-Dlibrary=shared_library" "-Dvisibility=default" "-Dnode_root_dir=/Users/hendryyeh/Library/Caches/node-gyp/24.4.0" "-Dnode_gyp_dir=/opt/homebrew/lib/node_modules/npm/node_modules/node-gyp" "-Dnode_lib_file=/Users/hendryyeh/Library/Caches/node-gyp/24.4.0/<(target_arch)/node.lib" "-Dmodule_root_dir=/Users/hendryyeh/Workspace/CodeVibe/.agent-worktrees/claude-2/codevibe-codex-plugin-e1/node_modules/fs-ext" "-Dnode_engine=v8" "--depth=." "-Goutput_dir=." "--generator-output=build" -I/Users/hendryyeh/Workspace/CodeVibe/.agent-worktrees/claude-2/codevibe-codex-plugin-e1/node_modules/fs-ext/build/config.gypi -I/opt/homebrew/lib/node_modules/npm/node_modules/node-gyp/addon.gypi -I/Users/hendryyeh/Library/Caches/node-gyp/24.4.0/include/node/common.gypi "--toplevel-dir=." binding.gyp
335
- Makefile: $(srcdir)/../../../../../../../Library/Caches/node-gyp/24.4.0/include/node/common.gypi $(srcdir)/../../../../../../../../../opt/homebrew/lib/node_modules/npm/node_modules/node-gyp/addon.gypi $(srcdir)/build/config.gypi $(srcdir)/binding.gyp
334
+ cmd_regen_makefile = cd $(srcdir); /opt/homebrew/lib/node_modules/npm/node_modules/node-gyp/gyp/gyp_main.py -fmake --ignore-environment "-Dlibrary=shared_library" "-Dvisibility=default" "-Dnode_root_dir=/Users/hendryyeh/Library/Caches/node-gyp/24.4.0" "-Dnode_gyp_dir=/opt/homebrew/lib/node_modules/npm/node_modules/node-gyp" "-Dnode_lib_file=/Users/hendryyeh/Library/Caches/node-gyp/24.4.0/<(target_arch)/node.lib" "-Dmodule_root_dir=/Users/hendryyeh/Workspace/CodeVibe/.agent-worktrees/claude-2/codevibe-codex-plugin/node_modules/fs-ext" "-Dnode_engine=v8" "--depth=." "-Goutput_dir=." "--generator-output=build" -I/Users/hendryyeh/Workspace/CodeVibe/.agent-worktrees/claude-2/codevibe-codex-plugin/node_modules/fs-ext/build/config.gypi -I/opt/homebrew/lib/node_modules/npm/node_modules/node-gyp/addon.gypi -I/Users/hendryyeh/Library/Caches/node-gyp/24.4.0/include/node/common.gypi "--toplevel-dir=." binding.gyp
335
+ Makefile: $(srcdir)/../../../../../../../../../opt/homebrew/lib/node_modules/npm/node_modules/node-gyp/addon.gypi $(srcdir)/build/config.gypi $(srcdir)/../../../../../../../Library/Caches/node-gyp/24.4.0/include/node/common.gypi $(srcdir)/binding.gyp
336
336
  $(call do_cmd,regen_makefile)
337
337
 
338
338
  # "all" is a concatenation of the "all" targets from all the included
@@ -490,13 +490,12 @@
490
490
  "python": "/opt/homebrew/opt/python@3.14/bin/python3.14",
491
491
  "standalone_static_library": 1,
492
492
  "global_prefix": "/opt/homebrew",
493
- "local_prefix": "/Users/hendryyeh/Workspace/CodeVibe/.agent-worktrees/claude-2/codevibe-codex-plugin-e1",
493
+ "local_prefix": "/Users/hendryyeh/Workspace/CodeVibe/.agent-worktrees/claude-2/codevibe-codex-plugin",
494
494
  "globalconfig": "/opt/homebrew/etc/npmrc",
495
495
  "init_module": "/Users/hendryyeh/.npm-init.js",
496
496
  "userconfig": "/Users/hendryyeh/.npmrc",
497
497
  "npm_version": "11.4.2",
498
498
  "node_gyp": "/opt/homebrew/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js",
499
- "save_exact": "true",
500
499
  "cache": "/Users/hendryyeh/.npm",
501
500
  "user_agent": "npm/11.4.2 node/v24.4.0 darwin arm64 workspaces/false",
502
501
  "prefix": "/opt/homebrew"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quantiya/codevibe-codex-plugin",
3
- "version": "2.0.8",
3
+ "version": "2.0.10",
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
  "codevibe": {