@quantiya/codevibe-claude-plugin 2.0.23 → 2.0.25
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/.claude-plugin/plugin.json +1 -1
- package/dist/server.js +7 -7
- package/node_modules/@quantiya/codevibe-core/dist/appsync/appsync-client.d.ts +25 -0
- package/node_modules/@quantiya/codevibe-core/dist/appsync/index.d.ts +1 -1
- package/node_modules/@quantiya/codevibe-core/dist/config/config.d.ts +1 -1
- package/node_modules/@quantiya/codevibe-core/dist/e1/__tests__/prompt-publication.test.d.ts +1 -0
- package/node_modules/@quantiya/codevibe-core/dist/e1/index.d.ts +1 -0
- package/node_modules/@quantiya/codevibe-core/dist/e1/prompt-publication.d.ts +14 -0
- package/node_modules/@quantiya/codevibe-core/dist/index.d.ts +1 -1
- package/node_modules/@quantiya/codevibe-core/dist/index.js +289 -289
- package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/__tests__/quorum-loop-outcome-recency.test.d.ts +1 -0
- package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/cli.d.ts +7 -0
- package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/cli.js +388 -121
- package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/quorum-loop.d.ts +4 -0
- package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/types.d.ts +24 -0
- package/node_modules/@quantiya/codevibe-core/dist/ordinal-presentation.d.ts +4 -0
- package/node_modules/@quantiya/codevibe-core/dist/ordinal-presentation.test.d.ts +1 -0
- package/node_modules/@quantiya/codevibe-core/package.json +1 -1
- package/node_modules/fs-ext/build/Makefile +1 -1
- package/node_modules/fs-ext/build/Release/fs_ext.node +0 -0
- package/package.json +2 -2
- package/node_modules/@iarna/toml/CHANGELOG.md +0 -278
- package/node_modules/color-convert/CHANGELOG.md +0 -54
- package/node_modules/cross-spawn/node_modules/which/CHANGELOG.md +0 -166
- package/node_modules/deep-extend/CHANGELOG.md +0 -46
- package/node_modules/es-toolkit/CHANGELOG.md +0 -1018
- package/node_modules/es-toolkit/src/compat/_internal/Equals.d.ts +0 -1
- package/node_modules/es-toolkit/src/compat/_internal/IsWritable.d.ts +0 -3
- package/node_modules/es-toolkit/src/compat/_internal/MutableList.d.ts +0 -4
- package/node_modules/es-toolkit/src/compat/_internal/RejectReadonly.d.ts +0 -4
- package/node_modules/keytar/build/Release/keytar.node +0 -0
package/dist/server.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
"use strict";var wt=Object.create;var ce=Object.defineProperty;var It=Object.getOwnPropertyDescriptor;var Pt=Object.getOwnPropertyNames;var Et=Object.getPrototypeOf,St=Object.prototype.hasOwnProperty;var bt=(u,e)=>{for(var t in e)ce(u,t,{get:e[t],enumerable:!0})},Fe=(u,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of Pt(e))!St.call(u,i)&&i!==t&&ce(u,i,{get:()=>e[i],enumerable:!(n=It(e,i))||n.enumerable});return u};var N=(u,e,t)=>(t=u!=null?wt(Et(u)):{},Fe(e||!u||!u.__esModule?ce(t,"default",{value:u,enumerable:!0}):t,u)),kt=u=>Fe(ce({},"__esModule",{value:!0}),u);var Zt={};bt(Zt,{McpServer:()=>Pe,classifyApplyUserDecisionEvent:()=>mt,classifyOrchestrationResponse:()=>Jt,classifyTmuxHasSessionError:()=>pt,formatOrchestrationBannerText:()=>ht,parseInteractivePromptInput:()=>ft,shouldFallThroughForBridgeUnavailable:()=>gt,shutdownExitCode:()=>ut});module.exports=kt(Zt);var $=N(require("fs")),L=N(require("path")),V=N(require("os")),Ee=require("child_process"),Oe=require("util"),dt=require("child_process"),te=require("crypto");var Qe=N(require("os")),Ne=N(require("path")),De=require("@quantiya/codevibe-core"),s=(0,De.createLogger)({name:"codevibe-claude",logFile:Ne.default.join(Qe.default.tmpdir(),"codevibe-claude-mcp.log"),level:"info"});var B=require("node:fs"),qe=N(require("node:os")),be=N(require("node:path")),pe=require("node:crypto"),Ke=448,Le=384,Be=1,se=class extends Error{constructor(t,n,i){super(n);this.claudeSessionId=t;this.code="CLAUDE_SESSION_BINDING_READ_FAILED";this.name="ClaudeSessionBindingReadError",this.cause=i}};function re(u){return(0,pe.createHash)("sha256").update(u).digest("hex")}function $e(u,e,t){if(!u||typeof u!="object")return!1;let n=u;return n.version===Be&&n.accountHash===e&&n.claudeSessionId===t&&typeof n.backendSessionId=="string"&&n.backendSessionId.length>0&&n.backendSessionId.length<=256&&/^claude-[A-Za-z0-9._:-]+$/.test(n.backendSessionId)}function ke(u,e,t){if(!u||!e||!t)throw new Error("Cannot derive Claude replacement identity from an empty component");let n=re(`${u}\0${e}\0${t}`).slice(0,32);return`claude-${e}-r${n}`}var de=class{constructor(e,t){this.logger=e,this.root=t??be.join(qe.homedir(),".codevibe","claude","backend-session-bindings")}async read(e,t){let n=re(e),i=this.recordPath(n,t),r;try{r=await B.promises.readFile(i,"utf8")}catch(c){if(c.code==="ENOENT")return null;throw this.logger.warn("Failed to read durable Claude backend-session binding",{claudeSessionId:t,error:String(c)}),new se(t,`Durable Claude backend-session binding is unavailable for ${t}`,c)}let o;try{o=JSON.parse(r)}catch(c){throw this.logger.warn("Failed to parse durable Claude backend-session binding",{claudeSessionId:t,error:String(c)}),new se(t,`Durable Claude backend-session binding is unreadable for ${t}`,c)}if(!$e(o,n,t))throw this.logger.warn("Rejected invalid durable Claude backend-session binding",{claudeSessionId:t}),new se(t,`Durable Claude backend-session binding is invalid for ${t}`);return o.backendSessionId}async write(e,t,n){let i=re(e),r={version:Be,accountHash:i,claudeSessionId:t,backendSessionId:n};if(!$e(r,i,t))throw new Error("Refusing to persist an invalid Claude backend-session binding");await B.promises.mkdir(this.root,{recursive:!0,mode:Ke}),process.platform!=="win32"&&await B.promises.chmod(this.root,Ke).catch(()=>{});let o=this.recordPath(i,t),c=`${o}.tmp.${process.pid}.${(0,pe.randomBytes)(6).toString("hex")}`,a=null;try{a=await B.promises.open(c,B.constants.O_WRONLY|B.constants.O_CREAT|B.constants.O_EXCL,Le),await a.writeFile(JSON.stringify(r),"utf8"),await a.sync(),await a.close(),a=null,process.platform!=="win32"&&await B.promises.chmod(c,Le),await B.promises.rename(c,o),await this.syncDirectory()}catch(l){throw a&&await a.close().catch(()=>{}),await B.promises.unlink(c).catch(()=>{}),l}}pathForTesting(e,t){return this.recordPath(re(e),t)}recordPath(e,t){let n=re(`${e}\0${t}`);return be.join(this.root,`${n}.json`)}async syncDirectory(){if(process.platform==="win32")return;let e=null;try{e=await B.promises.open(this.root,B.constants.O_RDONLY),await e.sync()}catch{}finally{e&&await e.close().catch(()=>{})}}};var K=require("node:fs"),ue=require("node:crypto"),He=N(require("node:os")),Te=N(require("node:path")),me=require("@quantiya/codevibe-core"),Ge=448,Ve=384,Tt=2;function At(u){return(0,ue.createHash)("sha256").update(u).digest("hex")}function Ct(u){if(u===void 0)return!0;if(!u||typeof u!="object")return!1;let e=u,t=e?.expectedIdentity,n=e?.invocation;return!!t&&(t.kind==="parsed"||t.kind==="ask_user_question")&&typeof t.key=="string"&&t.key.length>0&&!!n&&typeof n.occurrenceId=="string"&&n.occurrenceId.length>0&&typeof n.fingerprint=="string"&&Number.isInteger(n.generation)&&Array.isArray(n.e1PromptIds)&&n.e1PromptIds.every(i=>typeof i=="string"&&i.length>0)&&Number.isInteger(e?.attempts)&&(e?.attempts??-1)>=0&&typeof e?.nextAttemptAt=="number"&&Number.isFinite(e.nextAttemptAt)&&(e.dismissalSent===void 0||typeof e.dismissalSent=="boolean")}function Rt(u,e){if(!u||typeof u!="object")return!1;let t=u,n=t.event;return t.version===Tt&&t.nativeSessionId===e&&typeof t.sessionId=="string"&&t.sessionId.length>0&&(t.state==="queued"||t.state==="sending"||t.state==="submitted"||t.state==="terminal")&&Number.isInteger(t.attempts)&&(t.attempts??-1)>=0&&typeof t.nextAttemptAt=="number"&&Number.isFinite(t.nextAttemptAt)&&(t.terminalNoticeSent===void 0||typeof t.terminalNoticeSent=="boolean")&&!!n&&typeof n.eventId=="string"&&n.eventId.length>0&&typeof n.sessionId=="string"&&n.sessionId.length>0&&n.type===me.EventType.USER_PROMPT&&n.source===me.EventSource.MOBILE&&typeof n.content=="string"&&typeof n.timestamp=="string"&&Ct(t.pendingDismissal)}var le=class{constructor(e,t){this.mutations=new Map;this.logger=e,this.root=t??process.env.CODEVIBE_CLAUDE_MOBILE_QUEUE_ROOT??Te.join(He.homedir(),".codevibe","claude","mobile-prompt-queue")}async read(e){return this.readUnlocked(e)}async put(e,t){await this.mutate(e,n=>{let i=n.filter(r=>r.event.eventId!==t.event.eventId);return i.push(t),i})}async remove(e,t){await this.mutate(e,n=>n.filter(i=>i.event.eventId!==t))}pathForTesting(e){return this.recordPath(e)}async mutate(e,t){let i=(this.mutations.get(e)??Promise.resolve()).catch(()=>{}).then(async()=>{let r=t(await this.readUnlocked(e));await this.writeUnlocked(e,r)});this.mutations.set(e,i);try{await i}finally{this.mutations.get(e)===i&&this.mutations.delete(e)}}async readUnlocked(e){let t;try{t=await K.promises.readFile(this.recordPath(e),"utf8")}catch(i){if(i.code==="ENOENT")return[];throw i}let n;try{n=JSON.parse(t)}catch(i){throw this.logger.warn("Rejected unreadable durable mobile-prompt queue",{nativeSessionId:e,error:String(i)}),i}if(!Array.isArray(n)||!n.every(i=>Rt(i,e)))throw new Error(`Rejected invalid durable mobile-prompt queue for ${e}`);return n}async writeUnlocked(e,t){await K.promises.mkdir(this.root,{recursive:!0,mode:Ge}),process.platform!=="win32"&&await K.promises.chmod(this.root,Ge).catch(()=>{});let n=this.recordPath(e);if(t.length===0){await K.promises.unlink(n).catch(o=>{if(o.code!=="ENOENT")throw o});return}let i=`${n}.tmp.${process.pid}.${(0,ue.randomBytes)(6).toString("hex")}`,r=null;try{if(r=await K.promises.open(i,K.constants.O_WRONLY|K.constants.O_CREAT|K.constants.O_EXCL,Ve),await r.writeFile(JSON.stringify(t),"utf8"),await r.sync(),await r.close(),r=null,process.platform!=="win32"&&await K.promises.chmod(i,Ve),await K.promises.rename(i,n),process.platform!=="win32"){let o=await K.promises.open(this.root,K.constants.O_RDONLY).catch(()=>null);o&&(await o.sync().catch(()=>{}),await o.close().catch(()=>{}))}}catch(o){throw r&&await r.close().catch(()=>{}),await K.promises.unlink(i).catch(()=>{}),o}}recordPath(e){return Te.join(this.root,`${At(e)}.json`)}};var p=require("@quantiya/codevibe-core"),lt=require("@quantiya/quorum-core"),Ie=N(require("ws"));var Ae=N(require("express")),ne=N(require("fs")),Ce=N(require("path")),Re=N(require("os")),We=require("@quantiya/codevibe-core");var R=require("@quantiya/codevibe-core");var he=class{constructor(e=()=>({status:"healthy"})){this.assignedPort=0;this.healthProvider=e,this.app=(0,Ae.default)(),this.setupMiddleware(),this.setupRoutes()}setSessionId(e){this.sessionId=e}getPort(){return this.assignedPort}setupMiddleware(){this.app.use(Ae.default.json({limit:"1mb"})),this.app.use((e,t,n)=>{s.debug(`${e.method} ${e.path}`,{body:e.body,query:e.query}),n()}),this.app.use((e,t,n,i)=>{s.error("Express error:",e);let r={success:!1,error:e.message||"Internal server error"};n.status(500).json(r)})}setupRoutes(){this.app.get("/health",this.handleHealth.bind(this)),this.app.post("/event",this.handleEvent.bind(this)),process.env.NODE_ENV!=="production"&&this.app.post("/test/execute",this.handleTestExecute.bind(this))}handleHealth(e,t){let n=this.healthProvider();t.json({success:!0,service:"codevibe-claude",data:{...n,uptime:process.uptime(),version:"0.1.0",timestamp:new Date().toISOString()}})}async handleEvent(e,t){try{let n=e.body;if(!n.session_id){let o={success:!1,error:"Missing required field: session_id"};t.status(400).json(o);return}if(!n.hook_event_name){let o={success:!1,error:"Missing required field: hook_event_name"};t.status(400).json(o);return}let i=this.transformHookToEvent(n);i.received_at_ms=Date.now(),s.info("Received event from hook",{sessionId:n.session_id,hookEvent:n.hook_event_name,type:i.type}),this.eventHandler?await this.eventHandler(i):s.warn("No event handler registered");let r={success:!0,message:"Event processed successfully"};t.json(r)}catch(n){s.error("Error handling event:",n);let i={success:!1,error:n instanceof Error?n.message:"Unknown error"};t.status(500).json(i)}}async handleTestExecute(e,t){try{let{sessionId:n,prompt:i}=e.body;if(!n||!i){let o={success:!1,error:"Missing required fields: sessionId, prompt"};t.status(400).json(o);return}s.info("Test execute request",{sessionId:n,prompt:i});let r={success:!0,message:"Test execution endpoint - not implemented yet",data:{sessionId:n,prompt:i}};t.json(r)}catch(n){s.error("Error in test execute:",n);let i={success:!1,error:n instanceof Error?n.message:"Unknown error"};t.status(500).json(i)}}transformHookToEvent(e){let t,n,i={cwd:e.cwd,hook_event_name:e.hook_event_name,...e.metadata||{}};if(e.type&&e.content!==void 0)t=e.type,n=e.content;else switch(e.hook_event_name){case"SessionStart":t=R.EventType.NOTIFICATION,n="Session started",i.source=e.source;break;case"SessionEnd":t=R.EventType.NOTIFICATION,n=`Session ended: ${e.reason||"unknown"}`,i.reason=e.reason;break;case"UserPromptSubmit":t=R.EventType.USER_PROMPT,n=e.prompt||"";break;case"PostToolUse":t=R.EventType.TOOL_USE,n=JSON.stringify({tool_name:e.tool_name,tool_input:e.tool_input,tool_response:e.tool_response}),i.tool_name=e.tool_name;break;case"Notification":t=R.EventType.NOTIFICATION,n=e.message||"",i.notification_type=e.notification_type;break;default:t=R.EventType.NOTIFICATION,n=`Hook event: ${e.hook_event_name}`}return{session_id:e.session_id,hook_event_name:e.hook_event_name,type:t,source:R.EventSource.DESKTOP,content:n,metadata:i,tool_use_id:e.tool_use_id,tool_input:e.tool_input,transcript_path:e.transcript_path}}onEvent(e){this.eventHandler=e}async start(e){let t=e||this.sessionId;return t&&(this.sessionId=t),new Promise((n,i)=>{try{let r=(0,We.getConfig)(),o=r.server.dynamicPort?0:r.server.port;this.server=this.app.listen(o,r.server.host,()=>{let c=this.server.address();this.assignedPort=c.port,s.info(`HTTP API listening on http://${r.server.host}:${this.assignedPort}`),this.sessionId&&this.writePortFile(this.sessionId,this.assignedPort),n(this.assignedPort)}),this.server.on("error",c=>{s.error("HTTP server error:",c),i(c)})}catch(r){i(r)}})}writePortFile(e,t){let n=Ce.join(Re.tmpdir(),`codevibe-claude-${e}.port`);try{ne.writeFileSync(n,t.toString()),s.info(`Port file written: ${n} -> ${t}`)}catch(i){s.error(`Failed to write port file: ${n}`,i)}}removePortFile(){if(this.sessionId){let e=Ce.join(Re.tmpdir(),`codevibe-claude-${this.sessionId}.port`);try{ne.existsSync(e)&&(ne.unlinkSync(e),s.info(`Port file removed: ${e}`))}catch(t){s.warn(`Failed to remove port file: ${e}`,t)}}}async stop(e){return new Promise((t,n)=>{this.sessionId&&e?.protectedSessionIds?.has(this.sessionId)?s.info("Skipping port file removal \u2014 another daemon still serves this session",{sessionId:this.sessionId}):this.removePortFile(),this.server?this.server.close(i=>{i?(s.error("Error stopping HTTP server:",i),n(i)):(s.info("HTTP API stopped"),t())}):t()})}};var je=require("child_process"),ze=require("@quantiya/codevibe-core");var ge=class{async executePrompt(e,t){let n=(0,ze.getConfig)(),i=n.claude.defaultTimeout;return s.info("Executing prompt from mobile",{sessionId:e,promptLength:t.length,timeout:i}),new Promise(r=>{let o=["--resume",e,"--print","--output-format","stream-json",t];s.debug("Spawning Claude command",{command:n.claude.command,args:o});let c=(0,je.spawn)(n.claude.command,o,{stdio:["pipe","pipe","pipe"],shell:!0}),a="",l="",d=!1,P=setTimeout(()=>{d=!0,s.warn("Command execution timed out",{sessionId:e,timeout:i}),c.kill("SIGTERM")},i);c.stdout?.on("data",w=>{let f=w.toString();a+=f,s.debug("Command stdout",{output:f.slice(0,200)})}),c.stderr?.on("data",w=>{let f=w.toString();l+=f,s.debug("Command stderr",{output:f.slice(0,200)})}),c.on("close",w=>{clearTimeout(P);let f={success:w===0&&!d,output:a,error:l,exitCode:w||void 0,timedOut:d};f.success?s.info("Command executed successfully",{sessionId:e,exitCode:w,outputLength:a.length}):s.error("Command execution failed",{sessionId:e,exitCode:w,timedOut:d,error:l.slice(0,500)}),r(f)}),c.on("error",w=>{clearTimeout(P),s.error("Failed to spawn command",{error:w.message}),r({success:!1,error:w.message,timedOut:!1})})})}detectInteractivePrompt(e){return[/\[Y\/n\]/i,/\[y\/N\]/i,/\(y\/n\)/i,/Continue\?/i,/Proceed\?/i].some(n=>n.test(e))}extractPromptText(e){let t=e.split(`
|
|
1
|
+
"use strict";var wt=Object.create;var ce=Object.defineProperty;var It=Object.getOwnPropertyDescriptor;var Pt=Object.getOwnPropertyNames;var Et=Object.getPrototypeOf,bt=Object.prototype.hasOwnProperty;var St=(u,e)=>{for(var t in e)ce(u,t,{get:e[t],enumerable:!0})},Fe=(u,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of Pt(e))!bt.call(u,i)&&i!==t&&ce(u,i,{get:()=>e[i],enumerable:!(n=It(e,i))||n.enumerable});return u};var N=(u,e,t)=>(t=u!=null?wt(Et(u)):{},Fe(e||!u||!u.__esModule?ce(t,"default",{value:u,enumerable:!0}):t,u)),kt=u=>Fe(ce({},"__esModule",{value:!0}),u);var Zt={};St(Zt,{McpServer:()=>Pe,classifyApplyUserDecisionEvent:()=>mt,classifyOrchestrationResponse:()=>Jt,classifyTmuxHasSessionError:()=>pt,formatOrchestrationBannerText:()=>ht,parseInteractivePromptInput:()=>ft,shouldFallThroughForBridgeUnavailable:()=>gt,shutdownExitCode:()=>ut});module.exports=kt(Zt);var $=N(require("fs")),L=N(require("path")),V=N(require("os")),Ee=require("child_process"),Oe=require("util"),dt=require("child_process"),te=require("crypto");var Qe=N(require("os")),Ne=N(require("path")),De=require("@quantiya/codevibe-core"),s=(0,De.createLogger)({name:"codevibe-claude",logFile:Ne.default.join(Qe.default.tmpdir(),"codevibe-claude-mcp.log"),level:"info"});var B=require("node:fs"),qe=N(require("node:os")),Se=N(require("node:path")),pe=require("node:crypto"),Ke=448,Le=384,Be=1,se=class extends Error{constructor(t,n,i){super(n);this.claudeSessionId=t;this.code="CLAUDE_SESSION_BINDING_READ_FAILED";this.name="ClaudeSessionBindingReadError",this.cause=i}};function re(u){return(0,pe.createHash)("sha256").update(u).digest("hex")}function $e(u,e,t){if(!u||typeof u!="object")return!1;let n=u;return n.version===Be&&n.accountHash===e&&n.claudeSessionId===t&&typeof n.backendSessionId=="string"&&n.backendSessionId.length>0&&n.backendSessionId.length<=256&&/^claude-[A-Za-z0-9._:-]+$/.test(n.backendSessionId)}function ke(u,e,t){if(!u||!e||!t)throw new Error("Cannot derive Claude replacement identity from an empty component");let n=re(`${u}\0${e}\0${t}`).slice(0,32);return`claude-${e}-r${n}`}var de=class{constructor(e,t){this.logger=e,this.root=t??Se.join(qe.homedir(),".codevibe","claude","backend-session-bindings")}async read(e,t){let n=re(e),i=this.recordPath(n,t),r;try{r=await B.promises.readFile(i,"utf8")}catch(c){if(c.code==="ENOENT")return null;throw this.logger.warn("Failed to read durable Claude backend-session binding",{claudeSessionId:t,error:String(c)}),new se(t,`Durable Claude backend-session binding is unavailable for ${t}`,c)}let o;try{o=JSON.parse(r)}catch(c){throw this.logger.warn("Failed to parse durable Claude backend-session binding",{claudeSessionId:t,error:String(c)}),new se(t,`Durable Claude backend-session binding is unreadable for ${t}`,c)}if(!$e(o,n,t))throw this.logger.warn("Rejected invalid durable Claude backend-session binding",{claudeSessionId:t}),new se(t,`Durable Claude backend-session binding is invalid for ${t}`);return o.backendSessionId}async write(e,t,n){let i=re(e),r={version:Be,accountHash:i,claudeSessionId:t,backendSessionId:n};if(!$e(r,i,t))throw new Error("Refusing to persist an invalid Claude backend-session binding");await B.promises.mkdir(this.root,{recursive:!0,mode:Ke}),process.platform!=="win32"&&await B.promises.chmod(this.root,Ke).catch(()=>{});let o=this.recordPath(i,t),c=`${o}.tmp.${process.pid}.${(0,pe.randomBytes)(6).toString("hex")}`,a=null;try{a=await B.promises.open(c,B.constants.O_WRONLY|B.constants.O_CREAT|B.constants.O_EXCL,Le),await a.writeFile(JSON.stringify(r),"utf8"),await a.sync(),await a.close(),a=null,process.platform!=="win32"&&await B.promises.chmod(c,Le),await B.promises.rename(c,o),await this.syncDirectory()}catch(l){throw a&&await a.close().catch(()=>{}),await B.promises.unlink(c).catch(()=>{}),l}}pathForTesting(e,t){return this.recordPath(re(e),t)}recordPath(e,t){let n=re(`${e}\0${t}`);return Se.join(this.root,`${n}.json`)}async syncDirectory(){if(process.platform==="win32")return;let e=null;try{e=await B.promises.open(this.root,B.constants.O_RDONLY),await e.sync()}catch{}finally{e&&await e.close().catch(()=>{})}}};var K=require("node:fs"),ue=require("node:crypto"),He=N(require("node:os")),Te=N(require("node:path")),me=require("@quantiya/codevibe-core"),Ge=448,Ve=384,Tt=2;function At(u){return(0,ue.createHash)("sha256").update(u).digest("hex")}function Ct(u){if(u===void 0)return!0;if(!u||typeof u!="object")return!1;let e=u,t=e?.expectedIdentity,n=e?.invocation;return!!t&&(t.kind==="parsed"||t.kind==="ask_user_question")&&typeof t.key=="string"&&t.key.length>0&&!!n&&typeof n.occurrenceId=="string"&&n.occurrenceId.length>0&&typeof n.fingerprint=="string"&&Number.isInteger(n.generation)&&Array.isArray(n.e1PromptIds)&&n.e1PromptIds.every(i=>typeof i=="string"&&i.length>0)&&Number.isInteger(e?.attempts)&&(e?.attempts??-1)>=0&&typeof e?.nextAttemptAt=="number"&&Number.isFinite(e.nextAttemptAt)&&(e.dismissalSent===void 0||typeof e.dismissalSent=="boolean")}function Rt(u,e){if(!u||typeof u!="object")return!1;let t=u,n=t.event;return t.version===Tt&&t.nativeSessionId===e&&typeof t.sessionId=="string"&&t.sessionId.length>0&&(t.state==="queued"||t.state==="sending"||t.state==="submitted"||t.state==="terminal")&&Number.isInteger(t.attempts)&&(t.attempts??-1)>=0&&typeof t.nextAttemptAt=="number"&&Number.isFinite(t.nextAttemptAt)&&(t.terminalNoticeSent===void 0||typeof t.terminalNoticeSent=="boolean")&&!!n&&typeof n.eventId=="string"&&n.eventId.length>0&&typeof n.sessionId=="string"&&n.sessionId.length>0&&n.type===me.EventType.USER_PROMPT&&n.source===me.EventSource.MOBILE&&typeof n.content=="string"&&typeof n.timestamp=="string"&&Ct(t.pendingDismissal)}var le=class{constructor(e,t){this.mutations=new Map;this.logger=e,this.root=t??process.env.CODEVIBE_CLAUDE_MOBILE_QUEUE_ROOT??Te.join(He.homedir(),".codevibe","claude","mobile-prompt-queue")}async read(e){return this.readUnlocked(e)}async put(e,t){await this.mutate(e,n=>{let i=n.filter(r=>r.event.eventId!==t.event.eventId);return i.push(t),i})}async remove(e,t){await this.mutate(e,n=>n.filter(i=>i.event.eventId!==t))}pathForTesting(e){return this.recordPath(e)}async mutate(e,t){let i=(this.mutations.get(e)??Promise.resolve()).catch(()=>{}).then(async()=>{let r=t(await this.readUnlocked(e));await this.writeUnlocked(e,r)});this.mutations.set(e,i);try{await i}finally{this.mutations.get(e)===i&&this.mutations.delete(e)}}async readUnlocked(e){let t;try{t=await K.promises.readFile(this.recordPath(e),"utf8")}catch(i){if(i.code==="ENOENT")return[];throw i}let n;try{n=JSON.parse(t)}catch(i){throw this.logger.warn("Rejected unreadable durable mobile-prompt queue",{nativeSessionId:e,error:String(i)}),i}if(!Array.isArray(n)||!n.every(i=>Rt(i,e)))throw new Error(`Rejected invalid durable mobile-prompt queue for ${e}`);return n}async writeUnlocked(e,t){await K.promises.mkdir(this.root,{recursive:!0,mode:Ge}),process.platform!=="win32"&&await K.promises.chmod(this.root,Ge).catch(()=>{});let n=this.recordPath(e);if(t.length===0){await K.promises.unlink(n).catch(o=>{if(o.code!=="ENOENT")throw o});return}let i=`${n}.tmp.${process.pid}.${(0,ue.randomBytes)(6).toString("hex")}`,r=null;try{if(r=await K.promises.open(i,K.constants.O_WRONLY|K.constants.O_CREAT|K.constants.O_EXCL,Ve),await r.writeFile(JSON.stringify(t),"utf8"),await r.sync(),await r.close(),r=null,process.platform!=="win32"&&await K.promises.chmod(i,Ve),await K.promises.rename(i,n),process.platform!=="win32"){let o=await K.promises.open(this.root,K.constants.O_RDONLY).catch(()=>null);o&&(await o.sync().catch(()=>{}),await o.close().catch(()=>{}))}}catch(o){throw r&&await r.close().catch(()=>{}),await K.promises.unlink(i).catch(()=>{}),o}}recordPath(e){return Te.join(this.root,`${At(e)}.json`)}};var p=require("@quantiya/codevibe-core"),lt=require("@quantiya/quorum-core"),Ie=N(require("ws"));var Ae=N(require("express")),ne=N(require("fs")),Ce=N(require("path")),Re=N(require("os")),We=require("@quantiya/codevibe-core");var R=require("@quantiya/codevibe-core");var he=class{constructor(e=()=>({status:"healthy"})){this.assignedPort=0;this.healthProvider=e,this.app=(0,Ae.default)(),this.setupMiddleware(),this.setupRoutes()}setSessionId(e){this.sessionId=e}getPort(){return this.assignedPort}setupMiddleware(){this.app.use(Ae.default.json({limit:"1mb"})),this.app.use((e,t,n)=>{s.debug(`${e.method} ${e.path}`,{body:e.body,query:e.query}),n()}),this.app.use((e,t,n,i)=>{s.error("Express error:",e);let r={success:!1,error:e.message||"Internal server error"};n.status(500).json(r)})}setupRoutes(){this.app.get("/health",this.handleHealth.bind(this)),this.app.post("/event",this.handleEvent.bind(this)),process.env.NODE_ENV!=="production"&&this.app.post("/test/execute",this.handleTestExecute.bind(this))}handleHealth(e,t){let n=this.healthProvider();t.json({success:!0,service:"codevibe-claude",data:{...n,uptime:process.uptime(),version:"0.1.0",timestamp:new Date().toISOString()}})}async handleEvent(e,t){try{let n=e.body;if(!n.session_id){let o={success:!1,error:"Missing required field: session_id"};t.status(400).json(o);return}if(!n.hook_event_name){let o={success:!1,error:"Missing required field: hook_event_name"};t.status(400).json(o);return}let i=this.transformHookToEvent(n);i.received_at_ms=Date.now(),s.info("Received event from hook",{sessionId:n.session_id,hookEvent:n.hook_event_name,type:i.type}),this.eventHandler?await this.eventHandler(i):s.warn("No event handler registered");let r={success:!0,message:"Event processed successfully"};t.json(r)}catch(n){s.error("Error handling event:",n);let i={success:!1,error:n instanceof Error?n.message:"Unknown error"};t.status(500).json(i)}}async handleTestExecute(e,t){try{let{sessionId:n,prompt:i}=e.body;if(!n||!i){let o={success:!1,error:"Missing required fields: sessionId, prompt"};t.status(400).json(o);return}s.info("Test execute request",{sessionId:n,prompt:i});let r={success:!0,message:"Test execution endpoint - not implemented yet",data:{sessionId:n,prompt:i}};t.json(r)}catch(n){s.error("Error in test execute:",n);let i={success:!1,error:n instanceof Error?n.message:"Unknown error"};t.status(500).json(i)}}transformHookToEvent(e){let t,n,i={cwd:e.cwd,hook_event_name:e.hook_event_name,...e.metadata||{}};if(e.type&&e.content!==void 0)t=e.type,n=e.content;else switch(e.hook_event_name){case"SessionStart":t=R.EventType.NOTIFICATION,n="Session started",i.source=e.source;break;case"SessionEnd":t=R.EventType.NOTIFICATION,n=`Session ended: ${e.reason||"unknown"}`,i.reason=e.reason;break;case"UserPromptSubmit":t=R.EventType.USER_PROMPT,n=e.prompt||"";break;case"PostToolUse":t=R.EventType.TOOL_USE,n=JSON.stringify({tool_name:e.tool_name,tool_input:e.tool_input,tool_response:e.tool_response}),i.tool_name=e.tool_name;break;case"Notification":t=R.EventType.NOTIFICATION,n=e.message||"",i.notification_type=e.notification_type;break;default:t=R.EventType.NOTIFICATION,n=`Hook event: ${e.hook_event_name}`}return{session_id:e.session_id,hook_event_name:e.hook_event_name,type:t,source:R.EventSource.DESKTOP,content:n,metadata:i,tool_use_id:e.tool_use_id,tool_input:e.tool_input,transcript_path:e.transcript_path}}onEvent(e){this.eventHandler=e}async start(e){let t=e||this.sessionId;return t&&(this.sessionId=t),new Promise((n,i)=>{try{let r=(0,We.getConfig)(),o=r.server.dynamicPort?0:r.server.port;this.server=this.app.listen(o,r.server.host,()=>{let c=this.server.address();this.assignedPort=c.port,s.info(`HTTP API listening on http://${r.server.host}:${this.assignedPort}`),this.sessionId&&this.writePortFile(this.sessionId,this.assignedPort),n(this.assignedPort)}),this.server.on("error",c=>{s.error("HTTP server error:",c),i(c)})}catch(r){i(r)}})}writePortFile(e,t){let n=Ce.join(Re.tmpdir(),`codevibe-claude-${e}.port`);try{ne.writeFileSync(n,t.toString()),s.info(`Port file written: ${n} -> ${t}`)}catch(i){s.error(`Failed to write port file: ${n}`,i)}}removePortFile(){if(this.sessionId){let e=Ce.join(Re.tmpdir(),`codevibe-claude-${this.sessionId}.port`);try{ne.existsSync(e)&&(ne.unlinkSync(e),s.info(`Port file removed: ${e}`))}catch(t){s.warn(`Failed to remove port file: ${e}`,t)}}}async stop(e){return new Promise((t,n)=>{this.sessionId&&e?.protectedSessionIds?.has(this.sessionId)?s.info("Skipping port file removal \u2014 another daemon still serves this session",{sessionId:this.sessionId}):this.removePortFile(),this.server?this.server.close(i=>{i?(s.error("Error stopping HTTP server:",i),n(i)):(s.info("HTTP API stopped"),t())}):t()})}};var je=require("child_process"),ze=require("@quantiya/codevibe-core");var ge=class{async executePrompt(e,t){let n=(0,ze.getConfig)(),i=n.claude.defaultTimeout;return s.info("Executing prompt from mobile",{sessionId:e,promptLength:t.length,timeout:i}),new Promise(r=>{let o=["--resume",e,"--print","--output-format","stream-json",t];s.debug("Spawning Claude command",{command:n.claude.command,args:o});let c=(0,je.spawn)(n.claude.command,o,{stdio:["pipe","pipe","pipe"],shell:!0}),a="",l="",d=!1,P=setTimeout(()=>{d=!0,s.warn("Command execution timed out",{sessionId:e,timeout:i}),c.kill("SIGTERM")},i);c.stdout?.on("data",w=>{let f=w.toString();a+=f,s.debug("Command stdout",{output:f.slice(0,200)})}),c.stderr?.on("data",w=>{let f=w.toString();l+=f,s.debug("Command stderr",{output:f.slice(0,200)})}),c.on("close",w=>{clearTimeout(P);let f={success:w===0&&!d,output:a,error:l,exitCode:w||void 0,timedOut:d};f.success?s.info("Command executed successfully",{sessionId:e,exitCode:w,outputLength:a.length}):s.error("Command execution failed",{sessionId:e,exitCode:w,timedOut:d,error:l.slice(0,500)}),r(f)}),c.on("error",w=>{clearTimeout(P),s.error("Failed to spawn command",{error:w.message}),r({success:!1,error:w.message,timedOut:!1})})})}detectInteractivePrompt(e){return[/\[Y\/n\]/i,/\[y\/N\]/i,/\(y\/n\)/i,/Continue\?/i,/Proceed\?/i].some(n=>n.test(e))}extractPromptText(e){let t=e.split(`
|
|
2
2
|
`);for(let n=t.length-1;n>=0;n--){let i=t[n].trim();if(this.detectInteractivePrompt(i))return i}return null}};var Ye=require("child_process"),Xe=require("util");var _e=(0,Xe.promisify)(Ye.exec),fe=class{async answerInteractivePrompt(e,t,n={}){let{pressEnter:i=!0}=n;s.info("Attempting to answer interactive prompt",{sessionId:e,response:t,pressEnter:i});try{let r=process.env.CODEVIBE_TMUX_SESSION;return s.info("Checking tmux session environment",{tmuxSession:r||"(not set)",allEnvKeys:Object.keys(process.env).filter(o=>o.includes("CODEVIBE")||o.includes("TMUX"))}),r?(s.info("Using tmux send-keys",{tmuxSession:r,pressEnter:i}),await this.sendViaTmux(r,t,i),s.info("Successfully sent response to interactive prompt",{sessionId:e,response:t,pressEnter:i}),!0):(s.error("No tmux session found - CodeVibe Companion launch is required",{sessionId:e,hint:"Start Claude Code using `codevibe --agent claude`"}),!1)}catch(r){return s.error("Failed to answer interactive prompt",{sessionId:e,error:r instanceof Error?r.message:String(r)}),!1}}async sendViaTmux(e,t,n){let i=t.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\$/g,"\\$").replace(/`/g,"\\`");s.info("Sending via tmux",{sessionName:e,inputLength:t.length,pressEnter:n});try{let r=`tmux send-keys -t "${e}" -l "${i}"`,o=await _e(r);if(s.info("tmux send-keys (text) completed",{stdout:o.stdout||"(empty)",stderr:o.stderr||"(empty)"}),n){await this.delay(500);let c=`tmux send-keys -t "${e}" Enter`,a=await _e(c);s.info("tmux send-keys (Enter) completed",{stdout:a.stdout||"(empty)",stderr:a.stderr||"(empty)"})}else s.info("tmux send-keys: skipping Enter (caller requested digit-only)")}catch(r){throw s.error("tmux send-keys failed",{sessionName:e,error:r}),r}}async sendKey(e,t){let n=process.env.CODEVIBE_TMUX_SESSION;if(!n)return s.error("No tmux session found for sendKey",{sessionId:e,keyName:t}),!1;try{let i=`tmux send-keys -t "${n}" ${t}`,r=await _e(i);return s.info("tmux send-keys (single key) completed",{sessionId:e,keyName:t,stdout:r.stdout||"(empty)",stderr:r.stderr||"(empty)"}),!0}catch(i){return s.error("tmux send-keys (single key) failed",{sessionId:e,keyName:t,error:i instanceof Error?i.message:String(i)}),!1}}delay(e){return new Promise(t=>setTimeout(t,e))}isPromptResponse(e){let t=e.trim().toLowerCase();return!!(t==="y"||t==="n"||t==="yes"||t==="no"||/^[0-9]+$/.test(t)||/^[a-z]$/.test(t)||["exit","quit","q","continue","skip","abort","retry","cancel"].includes(t))}};var D=require("@quantiya/codevibe-core");var et="Claude Code reported an API error",_t=["Session started","sent to Claude Code","sent to agent","sent to interactive prompt","waiting for your input"],Ot=["null","notification"];function xt(u){return tt(u).toUpperCase().toLowerCase().normalize("NFKD").replace(new RegExp("\\p{M}","gu"),"")}var Ut=/^[\s\u001C-\u001F\u0085]+|[\s\u001C-\u001F\u0085]+$/gu;function tt(u){return u.replace(Ut,"")}var Je="\u200B";function Ze(u){let e=u;for(let n of _t){if(!e.includes(n))continue;let i=n[0]+Je+n.slice(1);e=e.split(n).join(i)}let t=tt(e);if(t.length===0)return et;if(Ot.includes(xt(e))){let n=e.indexOf(t);e=e.slice(0,n)+Je+e.slice(n)}return e}function it(u){let e;try{e=JSON.parse(u)}catch{return null}if(!e||typeof e!="object"||e.type!=="assistant"||e.isApiErrorMessage!==!0||typeof e.uuid!="string"||e.uuid.length===0||typeof e.timestamp!="string"||Number.isNaN(Date.parse(e.timestamp)))return null;let t=e.message?.content,n=Array.isArray(t)?t.filter(r=>r&&r.type==="text"&&typeof r.text=="string").map(r=>r.text.trim()).filter(r=>r.length>0).join(`
|
|
3
3
|
|
|
4
4
|
`):typeof t=="string"?t.trim():"",i={uuid:e.uuid,timestamp:e.timestamp,text:n};return typeof e.error=="string"&&e.error.trim().length>0&&(i.error=e.error.trim()),typeof e.apiErrorStatus=="number"&&Number.isFinite(e.apiErrorStatus)&&(i.apiErrorStatus=e.apiErrorStatus),typeof e.apiErrorIsTransient=="boolean"&&(i.apiErrorIsTransient=e.apiErrorIsTransient),i}function nt(u){let e=u.text.length>0?u.text:et,t=u.error;if(!t)return Ze(e);let n=t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),i=new RegExp(`(^|[^A-Za-z0-9_])${n}($|[^A-Za-z0-9_])`).test(e);return Ze(i?e:`${e} (${t})`)}function rt(u,e){let t=Date.parse(u.timestamp);return Number.isFinite(t)&&Number.isFinite(e)&&t>=e}var st=3e3,Mt=8,Ft=10,Qt=100,Nt=3e3;function ot(u){let e=Buffer.byteLength(u.carryover);return{status:"partial",progressKey:`${u.fileIdentity.dev}:${u.fileIdentity.ino}:${u.size}:${u.endOfComplete}:${e}`,size:u.size,carryoverBytes:e,endOfComplete:u.endOfComplete}}var Dt="tool_activity",ye=class{constructor(e){this.consolidator=null;this.ledger=null;this.cursor=null;this.backendSessionId=null;this.transcriptPath=null;this.backstopTimer=null;this.backstopInFlight=null;this.hostingSinceMs=Number.POSITIVE_INFINITY;this.relayedApiErrorUuids=new Map;this.deps=e}get sessionId(){return this.backendSessionId}isActive(){return this.consolidator!==null}async start(e,t,n={}){let i=n.hostingSinceMs;this.hostingSinceMs=typeof i=="number"&&Number.isFinite(i)?i:Date.now(),this.relayedApiErrorUuids.clear(),this.backendSessionId=e,this.transcriptPath=t??null;let r=new D.ToolActivityOutbox({agent:"claude",logger:this.deps.logger,root:this.deps.root});this.ledger=new D.ToolActivityLedger({agent:"claude",sessionId:e,logger:this.deps.logger,root:this.deps.root}),t&&(this.cursor=new D.ToolReplayCursor({agent:"claude",sessionId:e,canonicalPath:t,logger:this.deps.logger,root:this.deps.root,readBytes:this.deps.replayReadBytes,maxFrameBytes:this.deps.replayMaxFrameBytes}),await this.cursor.load()),this.consolidator=new D.ToolActivityConsolidator({agent:"claude",sessionId:e,logger:this.deps.logger,outbox:r,ledger:this.ledger,transport:this.buildTransport()}),await this.consolidator.init(),this.cursor&&await this.consolidator.repairLostWindows(this.cursor);let o=this.deps.backstopIntervalMs??3e3;this.cursor&&o>0&&(this.backstopTimer=setInterval(()=>{this.runTranscriptBackstop().catch(c=>this.deps.logger.warn("[tool-activity] transcript backstop failed",{sessionId:e,err:String(c)}))},o),this.backstopTimer.unref?.()),this.deps.logger.info("[tool-activity] consolidator started",{sessionId:e,hasTranscript:!!t})}async observeHookTool(e){if(!this.consolidator||!e.toolUseId||!e.toolName)return;let t=this.consolidator,n={id:e.toolUseId,tool:e.toolName,normalizedTarget:at(e.toolName,e.toolInput),ts:e.ts??new Date().toISOString(),byteOffset:0,...e.toolInput!==void 0?{digest:(0,D.digestOf)(e.toolInput)}:{}},i=await t.observe(n);return i==="closed"?await t.captureTerminal(n,this.deps.shutdownDrainMs??st)?"sealed-terminal":"closed":i}async runTranscriptBackstop(){if(this.backstopInFlight)return this.backstopInFlight;if(!this.consolidator||!this.cursor)return;let e=this.doTranscriptBackstop().then(()=>{});return this.backstopInFlight=e,e.catch(()=>{}).finally(()=>{this.backstopInFlight===e&&(this.backstopInFlight=null)}),e}async doTranscriptBackstop(e=!1){let t=this.consolidator,n=this.cursor;if(!t||!n)return"caught-up";this.ledger&&await this.ledger.reloadLegacyOwned();for(let i=0;i<Mt;i+=1){let r=await n.readFrames();if(r.missing)return"caught-up";if(r.frames.length===0)return r.carryover.length>0?ot(r):"caught-up";let o=Number.POSITIVE_INFINITY,c=e?{includeInFlight:!0}:void 0,a=[];for(let l of r.frames){for(let P of Kt(l.line,l.startOffset))await t.observe(P,c)==="deferred-in-flight"&&(o=Math.min(o,l.startOffset));let d=it(l.line);d&&rt(d,this.hostingSinceMs)&&a.push({err:d,offset:l.startOffset})}if(await t.flush(),await this.relayApiErrors(a),o<r.endOfComplete)return await n.checkpoint(o,{fileIdentity:r.fileIdentity,size:r.size,carryover:""}),this.pruneRelayedApiErrors(o),"more";if(await n.checkpoint(r.endOfComplete,{fileIdentity:r.fileIdentity,size:r.size,carryover:r.carryover}),this.pruneRelayedApiErrors(r.endOfComplete),!r.hasMore)return r.carryover.length>0?ot(r):"caught-up"}return"more"}hostingSince(){return this.hostingSinceMs}async relayApiErrors(e){let t=this.deps.onTranscriptApiError,n=this.backendSessionId;if(!t||!n||e.length===0)return;let i=this.deps.apiErrorRelayAwaitMs??Nt;for(let{err:r,offset:o}of e){if(this.relayedApiErrorUuids.has(r.uuid))continue;let c,a=new Promise(l=>{c=setTimeout(()=>l("timeout"),i),c.unref?.()});try{let l=await Promise.race([t(n,r),a]);if(l==="dropped"){this.deps.logger.warn("[api-error-relay] relay DROPPED fail-closed by the server (not delivered)",{sessionId:n,uuid:r.uuid,error:r.error});continue}this.relayedApiErrorUuids.set(r.uuid,o),l==="timeout"?this.deps.logger.warn("[api-error-relay] relay sink still pending past the await bound; continuing",{sessionId:n,uuid:r.uuid,error:r.error,awaitMs:i}):this.deps.logger.info("[api-error-relay] relayed transcript API error",{sessionId:n,uuid:r.uuid,error:r.error,apiErrorStatus:r.apiErrorStatus})}catch(l){this.deps.logger.warn("[api-error-relay] relay sink failed",{sessionId:n,uuid:r.uuid,error:r.error,err:String(l)})}finally{c&&clearTimeout(c)}}}pruneRelayedApiErrors(e){for(let[t,n]of this.relayedApiErrorUuids)n<e&&this.relayedApiErrorUuids.delete(t)}relayedApiErrorMemorySize(){return this.relayedApiErrorUuids.size}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??Qt)),t=null,n=0;for(;;){let i=await this.doTranscriptBackstop(!0);if(i==="caught-up")break;if(typeof i!="string"){if(i.progressKey===t?n+=1:(t=i.progressKey,n=1),n>=e){this.deps.logger.warn("[tool-activity] deferring unchanged incomplete transcript suffix during shutdown; the next owner will re-read it from the durable complete-line boundary",{size:i.size,carryoverBytes:i.carryoverBytes,endOfComplete:i.endOfComplete,unchangedTurns:n});break}await new Promise(r=>setTimeout(r,Ft))}else t=null,n=0,await new Promise(r=>setImmediate(r))}await this.consolidator?.drainSends(this.deps.shutdownDrainMs??st).catch(()=>{}),await this.consolidator?.shutdown(),this.consolidator=null,this.ledger=null,this.cursor=null}buildTransport(){let e=async(t,n,i)=>{let r=i?i.sessionKey:this.deps.getSessionKey();if(!r)throw new Error("[tool-activity] no session key at send (retryable)");let o=i?i.sessionKeyGen:this.deps.getSessionKeyGen();await this.deps.createEvent({sessionId:t,type:D.EventType.TOOL_ACTIVITY,source:D.EventSource.DESKTOP,content:this.deps.encryptContent(Dt,r),metadata:{encrypted:this.deps.encryptMetadata({manifest:n.manifest},r)},timestamp:n.windowStart,isEncrypted:!0,clientEventId:n.clientEventId,...o?{expectedSessionKeyGen:o}:{}})};return{send:async(t,n)=>{try{await e(t,n)}catch(i){if((0,D.isSessionKeyStaleError)(i)&&this.deps.refreshSessionKey){let r=await this.deps.refreshSessionKey(t);if(!r)throw i;await e(t,n,r);return}throw i}}}}};function Kt(u,e){let t;try{t=JSON.parse(u)}catch{return[]}let n=t?.message?.content;if(t?.type!=="assistant"||!Array.isArray(n))return[];let i=[];for(let r of n)r?.type==="tool_use"&&typeof r.id=="string"&&typeof r.name=="string"&&i.push({id:r.id,tool:r.name,normalizedTarget:at(r.name,r.input),ts:typeof t.timestamp=="string"?t.timestamp:new Date(0).toISOString(),byteOffset:e,...r.input!==void 0?{digest:(0,D.digestOf)(r.input)}:{}});return i}function at(u,e){if(!e||typeof e!="object")return;let t=e,n=i=>typeof i=="string"&&i.length>0?i:void 0;return n(t.file_path)??n(t.path)??n(t.pattern)??n(t.notebook_path)??n(t.url)??(typeof t.command=="string"?t.command.trim().split(/\s+/)[0]:void 0)}var we=(0,Oe.promisify)(dt.exec),Lt=(0,Oe.promisify)(Ee.execFile),$t="/exit",j="CODEVIBE_TMUX_SESSION",qt=1e3,Bt=1e4,ve=3,Gt=100;function ct(u,e){let t=u?.[e];return typeof t=="string"?t:Buffer.isBuffer(t)?t.toString("utf8"):""}function pt(u){if(typeof u?.code!="number")return"unknown";let e=`${ct(u,"stderr")}
|
|
5
|
-
${ct(u,"stdout")}`;return/^can't find session:/m.test(e)||/^no server running on /m.test(e)||/^error connecting to .* \((?:No such file or directory|Connection refused)\)$/m.test(e)?"absent":"unknown"}var Vt=1800*1e3,Ht=60*1e3,Wt=1440*60*1e3;async function jt(u,e){let t=async(n,i)=>{try{await we(n)}catch(r){s.warn("tmux send-keys failed during self-terminate",{sessionName:u,label:i,error:String(r)})}};await t(`tmux send-keys -t "${u}" C-c`,"ctrl-c"),await new Promise(n=>setTimeout(n,200)),await t(`tmux send-keys -t "${u}" -l "${e}"`,"quit-text"),await new Promise(n=>setTimeout(n,500)),await t(`tmux send-keys -t "${u}" Enter`,"enter")}var zt={question:"Ready to submit your answers?",options:[{label:"Submit answers",description:"Send your selections to the assistant"},{label:"Cancel",description:"Discard your answers"}],multiSelect:!1,_isSubmit:!0};function Yt(u){let e=u.length,t=e>=2,n=u.slice(1);return t&&n.push({...zt}),{questionCount:e,hasReviewScreen:t,remainingQueue:n}}var Pe=class u{constructor(e){this.activeSessions=new Map;this.assignedPort=0;this.portRefreshTimer=null;this.tmuxLifecycleTimer=null;this.tmuxLifecycleCheckInFlight=!1;this.tmuxLifecycleObservedAlive=!1;this.tmuxLifecycleInitDeadlineMs=Number.POSITIVE_INFINITY;this.stopPromise=null;this.e1TtlRefreshTimer=null;this.encryptedKeyRecoveryTimer=null;this.encryptedKeyRecoveryAttempt=0;this.encryptedKeyRecovery=null;this.sessionKey=null;this.sessionKeyGen=null;this.sessionIsEncrypted=!1;this.encryptionResolved=!0;this.toolActivity=null;this.toolActivityLegacyRecorders=new Map;this.claudeToBackendSessionId=new Map;this.sessionBindingStore=new de(s);this.sessionStartChain=Promise.resolve();this.sessionLifecycleQueueDepth=0;this.retiredSessionRecoveryPromises=new Map;this.lifecycleGeneration=0;this.isStopping=!1;this.currentClaudeSessionId=null;this.claudeOwnershipGeneration=0;this.claudeSessionIntentVersions=new Map;this.endedOrSupersededClaudeSessionIds=new Set;this.nestedClaudeSessionIds=new Set;this.pendingMobilePrompts=new Map;this.mobilePromptQueueStore=new le(s);this.queuedMobilePrompts=new Map;this.queuedMobilePromptDrains=new Set;this.queuedMobilePromptTimers=new Map;this.queuedMobilePromptAdmissions=new Map;this.e1PromptRaises=new Map;this.e1NeedsReRaise=new Set;this.e1NeedsResolution=new Set;this.e1ResolutionInFlight=new Map;this.e1ParkedOrphans=new Map;this.nextPromptGen=1;this.httpApi=new he(()=>this.getDaemonHealth()),this.commandExecutor=new ge,this.promptResponder=new fe,this.initialSessionId=e,this.currentClaudeSessionId=e??null}static{this.E1_TTL_REFRESH_MS=300*1e3}static{this.MOBILE_PROMPT_EXPIRY_MS=3e3}static{this.MOBILE_PROMPT_QUEUE_MAX_ATTEMPTS=8}static{this.MOBILE_PROMPT_QUEUE_MAX_BACKOFF_MS=3e4}newE1Raise(e,t,n){let{record:i}=(0,p.newPromptRaise)({sessionId:e,producerKind:"PLUGIN_APPROVAL",mobileActionable:t,agentType:"CLAUDE",...n!==void 0&&{title:n}});return this.e1PromptRaises.set(i.promptId,{record:i}),i}stashE1RaiseContent(e,t,n){let i=this.e1PromptRaises.get(e);i&&(i.contentPlain=t,i.metadataPlain=n)}snapshotOpenE1(e){return[...this.e1PromptRaises.values()].filter(t=>t.record.sessionId===e&&!this.e1NeedsResolution.has(t.record.promptId)).map(t=>({record:{...t.record},...t.contentPlain!==void 0&&{contentPlain:t.contentPlain},...t.metadataPlain!==void 0&&{metadataPlain:t.metadataPlain}}))}async reRaiseSnapshotE1(e,t,n){if(e.length===0)return[];s.info("E1: re-raising open prompts onto replacement session",{replacementSessionId:t,count:e.length});let i=[];for(let r of e){let o={record:{...r.record,sessionId:t},...r.contentPlain!==void 0&&{contentPlain:r.contentPlain},...r.metadataPlain!==void 0&&{metadataPlain:r.metadataPlain}};this.e1PromptRaises.set(o.record.promptId,o),await this.reRaiseOneE1(t,o,n?{untilAck:n}:void 0)||i.push(r)}return i}parkE1Orphans(e,t){if(t.length===0)return;let n=new Map;for(let i of this.e1ParkedOrphans.get(e)??[])n.set(i.record.promptId,i);for(let i of t)n.set(i.record.promptId,i);this.e1ParkedOrphans.set(e,[...n.values()]),s.info("E1: parked retired-session open prompts for replacement re-home",{claudeSessionId:e,count:n.size})}async drainParkedE1Orphans(e,t,n){let i=this.e1ParkedOrphans.get(e);if(!i||i.length===0)return;let r=await this.reRaiseSnapshotE1(i,t,n);if(r.length===0){this.e1ParkedOrphans.delete(e);return}this.e1ParkedOrphans.set(e,r),s.info("E1: retirement drain aborted before ACK \u2014 retained durable park for next bootstrap",{claudeSessionId:e,liveSessionId:t,retained:r.length})}async reRaiseOneE1(e,t,n){let i=t.record;if(this.e1NeedsResolution.has(i.promptId))return!1;let r=i.mobileActionable&&t.contentPlain!==void 0,o=n?.untilAck,c=4;for(let a=1;;a++){if(o&&!o())return this.e1NeedsReRaise.add(i.promptId),s.info("E1: re-raise wait aborted (lifecycle superseded/stopping) \u2014 flagged for TTL-drain",{sessionId:e,promptId:i.promptId}),!1;try{if(r){let l=this.encryptForEmit(t.contentPlain,t.metadataPlain??{},p.EventType.INTERACTIVE_PROMPT,e);if(!l)return!1;await this.appSyncClient.createEvent({sessionId:e,type:p.EventType.INTERACTIVE_PROMPT,source:p.EventSource.DESKTOP,content:l.content,metadata:l.metadata,...(0,p.raiseFieldsFromRecord)(i),timestamp:(0,p.prepareEventTimestamp)({orderingKey:e}),isEncrypted:l.isEncrypted?!0:void 0})}else if(i.mobileActionable=!1,!await this.emitSuppressedPromptCarrier(e,i))return!1;return this.e1NeedsReRaise.delete(i.promptId),s.info("E1: re-raised prompt onto replacement session",{sessionId:e,promptId:i.promptId,actionable:r}),!0}catch(l){if(!o&&a>=c)return this.e1NeedsReRaise.add(i.promptId),s.error("E1: re-raise failed after retries \u2014 flagged for TTL-drain re-attempt",{sessionId:e,promptId:i.promptId,error:l instanceof Error?l.message:String(l)}),!1;if(o){let d=Date.now()+Math.min(250*2**(a-1),4e3);for(;Date.now()<d&&o();)await new Promise(P=>setTimeout(P,Math.min(50,d-Date.now())))}else await new Promise(d=>setTimeout(d,250*a))}}}async drainPendingE1ReRaises(){for(let e of[...this.e1NeedsReRaise]){if(this.e1NeedsResolution.has(e))continue;let t=this.e1PromptRaises.get(e);if(!t){this.e1NeedsReRaise.delete(e);continue}this.activeSessions.has(t.record.sessionId)&&await this.reRaiseOneE1(t.record.sessionId,t)}}async resolveE1PluginApproval(e,t){let n=this.e1ResolutionInFlight.get(t);if(n)return n;let i=this.e1PromptRaises.get(t);if(!i)return this.e1NeedsResolution.delete(t),this.e1NeedsReRaise.delete(t),!0;this.e1NeedsResolution.add(t);let r=(async()=>{try{let o=await this.appSyncClient.createEvent({sessionId:i.record.sessionId||e,type:p.EventType.NOTIFICATION,source:p.EventSource.DESKTOP,resolvedPromptId:t,ownerToken:i.record.ownerToken});return o?.promptId!==t?(s.warn("E1: plugin prompt resolution returned without exact prompt acknowledgement",{sessionId:e,promptId:t,acknowledgedPromptId:o?.promptId}),!1):(this.e1NeedsResolution.delete(t),this.e1NeedsReRaise.delete(t),this.e1PromptRaises.delete(t),s.info("E1: plugin prompt resolution acknowledged",{sessionId:e,promptId:t}),!0)}catch(o){return s.warn("E1: plugin prompt resolution failed; retained for retry",{sessionId:e,promptId:t,error:o instanceof Error?o.message:String(o)}),!1}})();this.e1ResolutionInFlight.set(t,r);try{return await r}finally{this.e1ResolutionInFlight.get(t)===r&&this.e1ResolutionInFlight.delete(t)}}async clearInteractivePromptsForAcceptedInput(e){let t=[...this.e1PromptRaises.values()].filter(r=>r.record.sessionId===e.sessionId).map(r=>r.record.promptId);if(!!!(e.waitingForPromptResponse||e.activeAskUserQuestionInvocation||e.pendingAskUserQuestionInvocations?.length)&&t.length===0)return;let i=new Set(e.pendingAskUserQuestionInvocations?.map(r=>r.fingerprint)??[]);if(e.activeAskUserQuestionInvocation?.fingerprint&&i.add(e.activeAskUserQuestionInvocation.fingerprint),i.size>0){e.completedAskUserQuestionFingerprints||(e.completedAskUserQuestionFingerprints=new Set);for(let r of i)e.completedAskUserQuestionFingerprints.add(r)}this.clearPromptState(e),e.activeAskUserQuestionInvocation=void 0,e.pendingAskUserQuestionInvocations=[];for(let r of t)await this.resolveE1PluginApproval(e.sessionId,r)}async drainPendingE1Resolutions(){for(let e of[...this.e1NeedsResolution]){let t=this.e1PromptRaises.get(e);if(!t){this.e1NeedsResolution.delete(e);continue}await this.resolveE1PluginApproval(t.record.sessionId,e)}}getDaemonHealth(){let e=this.encryptedKeyRecovery;return e?{status:"degraded",reason:"encrypted_session_key_unavailable",sessionId:e.backendSessionId,retryAttempt:this.encryptedKeyRecoveryAttempt}:{status:"healthy"}}clearEncryptedKeyRecovery(e,t,n){t&&this.encryptedKeyRecovery?.claudeSessionId!==t||n&&this.encryptedKeyRecovery?.backendSessionId!==n||(this.encryptedKeyRecoveryTimer&&(clearTimeout(this.encryptedKeyRecoveryTimer),this.encryptedKeyRecoveryTimer=null),this.encryptedKeyRecovery&&s.info("Encrypted-session key recovery cleared",{claudeSessionId:this.encryptedKeyRecovery.claudeSessionId,backendSessionId:this.encryptedKeyRecovery.backendSessionId,reason:e}),this.encryptedKeyRecovery=null,this.encryptedKeyRecoveryAttempt=0)}enterEncryptedKeyRecovery(e,t){let n=e.session_id,i=this.encryptedKeyRecovery;return!i||i.claudeSessionId!==n||i.backendSessionId!==t?(this.clearEncryptedKeyRecovery("replaced by a different recovery target"),this.encryptedKeyRecovery={claudeSessionId:n,backendSessionId:t,payload:e,livenessInvalidated:!1}):(i.payload=e,i.livenessInvalidated=!1,this.encryptedKeyRecoveryTimer&&(clearTimeout(this.encryptedKeyRecoveryTimer),this.encryptedKeyRecoveryTimer=null)),this.encryptedKeyRecovery}async invalidateEncryptedRecoveryLiveness(e){for(;!this.isStopping&&this.encryptedKeyRecovery===e;){let t=await this.appSyncClient.invalidateHeartbeat(e.backendSessionId);if(this.isStopping||this.encryptedKeyRecovery!==e)return!1;if(t)return e.livenessInvalidated=!0,this.scheduleEncryptedKeyRecovery(e.payload,e.backendSessionId),!0;s.warn("Retrying durable invalidation before encrypted-key recovery",{claudeSessionId:e.claudeSessionId,backendSessionId:e.backendSessionId}),await new Promise(n=>setTimeout(n,1e3))}return!1}scheduleEncryptedKeyRecovery(e,t){if(this.isStopping)return;let n=e.session_id,i=this.encryptedKeyRecovery;if(!i||i.claudeSessionId!==n||i.backendSessionId!==t){let c=this.enterEncryptedKeyRecovery(e,t);this.invalidateEncryptedRecoveryLiveness(c);return}else i.payload=e;if(!i.livenessInvalidated||this.encryptedKeyRecoveryTimer)return;let r=Math.min(6e4,5e3*2**Math.min(this.encryptedKeyRecoveryAttempt,4));this.encryptedKeyRecoveryAttempt+=1;let o=this.encryptedKeyRecovery;o&&(s.warn("Encrypted session is keyless; scheduling automatic resume recovery",{claudeSessionId:n,backendSessionId:t,attempt:this.encryptedKeyRecoveryAttempt,delayMs:r}),this.encryptedKeyRecoveryTimer=setTimeout(()=>{this.encryptedKeyRecoveryTimer=null,!(this.isStopping||this.encryptedKeyRecovery!==o)&&this.handleSessionStart(o.payload,o.backendSessionId,"self-heal").catch(c=>{s.warn("Encrypted-session key recovery attempt failed",{claudeSessionId:n,backendSessionId:t,error:String(c)})}).finally(()=>{!this.isStopping&&this.encryptedKeyRecovery===o&&!this.encryptedKeyRecoveryTimer&&this.scheduleEncryptedKeyRecovery(o.payload,o.backendSessionId)})},r),this.encryptedKeyRecoveryTimer.unref?.())}getPort(){return this.assignedPort}generateBackendSessionId(e){return`claude-${e}`}trackMobilePrompt(e,t){this.pendingMobilePrompts.has(e)||this.pendingMobilePrompts.set(e,[]),this.pendingMobilePrompts.get(e).push({prompt:t.trim(),timestamp:Date.now()}),s.debug("Tracking mobile prompt for deduplication",{sessionId:e,promptLength:t.length})}isRecentMobilePrompt(e,t){let n=this.pendingMobilePrompts.get(e);if(!n)return!1;let i=Date.now(),r=t.trim(),o=[],c=!1;for(let a of n)if(!(i-a.timestamp>u.MOBILE_PROMPT_EXPIRY_MS)){if(!c&&a.prompt===r){c=!0,s.debug("Found matching mobile prompt, filtering duplicate",{sessionId:e});continue}o.push(a)}return o.length>0?this.pendingMobilePrompts.set(e,o):this.pendingMobilePrompts.delete(e),c}writePortFile(e){let t=L.join(V.tmpdir(),`codevibe-claude-${e}.port`);try{$.writeFileSync(t,this.assignedPort.toString()),s.info(`Port file written: ${t} -> ${this.assignedPort}`)}catch(n){s.error(`Failed to write port file: ${t}`,n)}}removePortFile(e){let t=L.join(V.tmpdir(),`codevibe-claude-${e}.port`);try{$.existsSync(t)&&($.unlinkSync(t),s.info(`Port file removed: ${t}`))}catch(n){s.warn(`Failed to remove port file: ${t}`,n)}}hasOtherLiveDaemonForSession(e){try{let t=(0,Ee.execSync)("ps -eww -o pid= -o args=",{encoding:"utf8",timeout:2e3}),n=process.pid;for(let i of t.split(`
|
|
6
|
-
`)){let r=i.trim();if(!r)continue;let o=r.indexOf(" ");if(o<0)continue;let c=parseInt(r.substring(0,o),10);if(isNaN(c)||c===n)continue;let a=r.substring(o+1);if(/node.*codevibe-claude.*server\.js/.test(a)&&a.includes(e))return!0}return!1}catch(t){return s.warn('hasOtherLiveDaemonForSession: ps query failed; falling back to "no other daemon"',{error:String(t)}),!1}}async start(){try{if(s.info("Starting CodeVibe MCP Server...",{environment:(0,p.getEnvironment)()}),this.appSyncClient=new p.AppSyncClient,await this.appSyncClient.authenticateWithStoredTokens()){s.info("Authenticated with stored OAuth tokens",{userId:this.appSyncClient.getCurrentUserId(),email:this.appSyncClient.getCurrentUserEmail()}),await(0,p.registerDeviceEncryptionKey)(this.appSyncClient,s),(0,p.startDeviceKeyWatcher)(this.appSyncClient,s),(0,p.pushDetectedAgents)(this.appSyncClient,s).catch(t=>{s.warn("Failed to update available agents (non-fatal)",{error:t?.message})});try{let t=await this.appSyncClient.sweepOrphanSessions({agentType:"CLAUDE"});t>0&&s.info("Orphan sweep: marked stale Claude sessions INACTIVE",{swept:t})}catch(t){s.warn("Orphan sweep failed, continuing startup",{error:t instanceof Error?t.message:String(t)})}}else s.error('Authentication failed. Run "codevibe login" first.'),console.error('Not authenticated. Run "codevibe login" to sign in.'),process.exit(1);this.httpApi.onEvent(this.handleEventFromHook.bind(this)),this.assignedPort=await this.httpApi.start(this.initialSessionId),this.startPortKeepalive(),this.startTmuxLifecycleMonitor(),s.info("MCP Server started successfully",{port:this.assignedPort,host:(0,p.getConfig)().server.host,dynamicPort:(0,p.getConfig)().server.dynamicPort,sessionId:this.initialSessionId,authenticated:this.appSyncClient.isAuthenticated(),userId:this.appSyncClient.getCurrentUserId()})}catch(e){throw s.error("Failed to start MCP Server:",e),e}}startPortKeepalive(){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(Wt,Math.max(Ht,e)):Vt;this.portRefreshTimer=setInterval(()=>this.refreshPortFiles(),t),this.e1TtlRefreshTimer&&clearInterval(this.e1TtlRefreshTimer),this.e1TtlRefreshTimer=setInterval(()=>{this.refreshE1Ttls()},u.E1_TTL_REFRESH_MS)}async refreshE1Ttls(){await this.drainPendingE1Resolutions(),await this.drainPendingE1ReRaises();let e=[...this.e1PromptRaises.values()].filter(t=>this.activeSessions.has(t.record.sessionId)&&!this.e1NeedsResolution.has(t.record.promptId)).map(t=>t.record.promptId);if(e.length!==0)try{let t=await this.appSyncClient.refreshOpenPromptTtl(e);for(let n of t){let i=this.e1PromptRaises.get(n);i&&this.activeSessions.has(i.record.sessionId)&&await this.reRaiseOneE1(i.record.sessionId,i)}}catch(t){s.warn("E1: TTL-refresh tick failed (non-fatal, retries next tick)",{error:t instanceof Error?t.message:String(t)})}}refreshPortFiles(){let e=new Date;for(let n of this.activeSessions.values()){let i=L.join(V.tmpdir(),`codevibe-claude-${n.claudeSessionId}.port`);try{$.utimesSync(i,e,e)}catch(o){o?.code==="ENOENT"?this.writePortFile(n.claudeSessionId):s.warn(`Port-file refresh failed: ${i}`,o)}let r=L.join(V.tmpdir(),`codevibe-claude-${n.claudeSessionId}.pid`);try{$.utimesSync(r,e,e)}catch(o){o?.code!=="ENOENT"&&s.warn(`Pid-file refresh failed: ${r}`,o)}}let t=process.env[j];if(t){let n=L.join(V.tmpdir(),`codevibe-claude-instance-${t}.json`);try{$.utimesSync(n,e,e)}catch(i){i?.code!=="ENOENT"&&s.warn(`Instance-map refresh failed: ${n}`,i)}}}startTmuxLifecycleMonitor(){if(this.tmuxLifecycleTimer)return;let e=process.env[j];e&&(this.tmuxLifecycleInitDeadlineMs=Date.now()+Bt,this.tmuxLifecycleTimer=setInterval(()=>{this.checkTmuxLifecycle(e)},qt),this.tmuxLifecycleTimer.unref?.(),this.checkTmuxLifecycle(e))}async tmuxSessionState(e){try{return await Lt("tmux",["has-session","-t",e]),"alive"}catch(t){let n=pt(t);return n==="absent"?n:(s.warn("Could not inspect native Claude tmux session; preserving daemon",{tmuxSession:e,error:t instanceof Error?t.message:String(t)}),"unknown")}}async checkTmuxLifecycle(e){if(!(this.isStopping||this.tmuxLifecycleCheckInFlight)){this.tmuxLifecycleCheckInFlight=!0;try{let t=await this.tmuxSessionState(e);if(t==="alive"){this.tmuxLifecycleObservedAlive=!0;return}let n=this.tmuxLifecycleObservedAlive||Date.now()>=this.tmuxLifecycleInitDeadlineMs;if(t==="unknown"||!n||this.isStopping)return;this.stopTmuxLifecycleMonitor(),s.info("Native Claude tmux session ended; stopping companion daemon",{tmuxSession:e}),this.stop().then(()=>process.exit(0),i=>{s.error("Failed to stop companion daemon after native session end",{error:i instanceof Error?i.message:String(i)}),process.exit(1)})}finally{this.tmuxLifecycleCheckInFlight=!1}}}stopTmuxLifecycleMonitor(){this.tmuxLifecycleTimer&&(clearInterval(this.tmuxLifecycleTimer),this.tmuxLifecycleTimer=null)}async shouldShutdownForSignal(e){if(e!=="SIGHUP")return!0;let t=process.env[j];if(!t)return!0;let n=await this.tmuxSessionState(t);return n==="absent"?!0:(s.info("Ignoring SIGHUP while native Claude tmux session remains available",{tmuxSession:t,state:n}),!1)}stop(){return this.stopPromise?this.stopPromise:(this.stopPromise=this.doStop(),this.stopPromise)}async doStop(){s.info("Stopping MCP Server..."),this.isStopping=!0,this.stopTmuxLifecycleMonitor(),this.lifecycleGeneration+=1,this.clearEncryptedKeyRecovery("daemon stopping"),await this.sessionStartChain.catch(()=>{}),this.portRefreshTimer&&(clearInterval(this.portRefreshTimer),this.portRefreshTimer=null),this.e1TtlRefreshTimer&&(clearInterval(this.e1TtlRefreshTimer),this.e1TtlRefreshTimer=null),this.toolActivity&&(await this.toolActivity.stop(),this.toolActivity=null);let e=Array.from(this.activeSessions.keys()),t=new Set,n=[];s.info(`Marking ${e.length} active session(s) as INACTIVE...`);for(let r of e){let o=this.activeSessions.get(r);o?.mobileEndWatcher&&(o.mobileEndWatcher.stop(),o.mobileEndWatcher=void 0),this.appSyncClient.stopHeartbeat(r)}for(let r of e){let o=this.activeSessions.get(r);try{if(o&&this.hasOtherLiveDaemonForSession(o.claudeSessionId)){s.info("Another daemon serves this session \u2014 skipping mark INACTIVE AND port file removal during shutdown",{sessionId:r,claudeSessionId:o.claudeSessionId,myPid:process.pid}),t.add(o.claudeSessionId);continue}try{await this.retireHostedSession(r),s.info("Session marked as INACTIVE during shutdown",{sessionId:r})}catch(c){n.push(r),s.error("Failed to mark session as INACTIVE during shutdown",{sessionId:r,error:c})}o&&this.removeOwnedSessionFiles(o.claudeSessionId)}catch(c){s.warn("Failed during shutdown cleanup",{sessionId:r,error:c})}}this.appSyncClient.cleanupSubscriptions(),this.activeSessions.clear(),await this.httpApi.stop({protectedSessionIds:t});let i=process.env[j];if(i&&t.size===0&&this.removeOwnedTmuxFiles(i),s.info("MCP Server stopped"),n.length>0)throw new Error(`Failed to retire hosted Claude sessions: ${n.join(", ")}`)}async retireHostedSession(e){let t;for(let n=1;n<=ve;n+=1)try{await this.appSyncClient.updateSession({sessionId:e,status:p.SessionStatus.INACTIVE});return}catch(i){t=i,s.warn("Failed to retire hosted Claude session",{sessionId:e,attempt:n,maxAttempts:ve,error:i instanceof Error?i.message:String(i)}),n<ve&&await new Promise(r=>setTimeout(r,Gt*n))}throw new Error(`Failed to retire hosted Claude session ${e} after ${ve} attempts: ${String(t)}`)}removeOwnedSessionFiles(e){this.removePortFile(e),this.removeOwnedFile(L.join(V.tmpdir(),`codevibe-claude-${e}.pid`)),this.removeOwnedFile(L.join(V.tmpdir(),`codevibe-claude-sent-uuids-${e}.txt`))}removeOwnedTmuxFiles(e){let t=/^codevibe-claude-(\d+)$/.exec(e);if(!t){s.warn("Refusing to remove Claude tmux lifecycle files for an unexpected session name",{tmuxSession:e});return}let n=t[1];this.removeOwnedFile(L.join(V.tmpdir(),`codevibe-claude-instance-${e}.json`)),this.removeOwnedFile(L.join(V.tmpdir(),`codevibe-claude-root-${e}.id`)),this.removeOwnedFile(L.join(V.tmpdir(),`codevibe-claude-tmux-session-${n}`))}removeOwnedFile(e){try{$.unlinkSync(e)}catch(t){t?.code!=="ENOENT"&&s.warn(`Failed to remove owned Claude lifecycle file: ${e}`,t)}}encryptForEmit(e,t,n,i){let r=this.activeSessions.get(i)?.encryptionSnapshot;if(!r)return s.error("No encryption snapshot for target session \u2014 dropping event (fail-closed, #638 H1)",{type:n,sessionId:i,sessionKnown:this.activeSessions.has(i)}),null;let o=(0,p.encryptForEmit)(r.sessionKey,r.sessionIsEncrypted,e,t,r.encryptionResolved);return o===null&&s.error("No session key for ENCRYPTED/unresolved session \u2014 dropping event (fail-closed, #638)",{type:n,sessionId:i,encryptionResolved:r.encryptionResolved}),o}async handleEventFromHook(e){let{session_id:t,hook_event_name:n,type:i,content:r}=e;if(s.info("Processing hook event",{sessionId:t,hookEvent:n,type:i}),n==="SessionStart"&&e.metadata?.source==="startup"&&this.currentClaudeSessionId!==null&&this.currentClaudeSessionId!==t&&this.nestedClaudeSessionIds.add(t),this.nestedClaudeSessionIds.has(t)){s.info("Ignoring hook from quarantined nested Claude session",{sessionId:t,hookEvent:n,owningSessionId:this.currentClaudeSessionId});return}let o,c;try{if(n==="SessionStart")await this.handleSessionStart(e);else if(n==="SessionEnd")await this.handleSessionEnd(e);else if(!this.activeSessions.has(this.claudeToBackendSessionId.get(t)||this.generateBackendSessionId(t))){s.warn("Hook event for un-bootstrapped session \u2014 self-healing via SessionStart (#638)",{claudeSessionId:t,hookEvent:n});let m=this.encryptedKeyRecovery?.claudeSessionId===t?this.encryptedKeyRecovery.backendSessionId:void 0;await this.handleSessionStart({session_id:t,metadata:e.metadata,transcript_path:e.transcript_path,received_at_ms:e.received_at_ms},m,"self-heal");let I=this.claudeToBackendSessionId.get(t)||this.generateBackendSessionId(t);if(!this.activeSessions.has(I)){s.warn("Dropping hook because Claude self-heal lifecycle intent is stale",{claudeSessionId:t,hookEvent:n});return}}let a=this.claudeToBackendSessionId.get(t)||this.generateBackendSessionId(t);if(n==="PostToolUse"&&e.metadata?.tool_name==="AskUserQuestion"){let m=this.activeSessions.get(a),I=this.computeAskUserQuestionFingerprint(e.tool_input?.questions),h=m?.pendingAskUserQuestionInvocations,y=I?h?.findIndex(v=>v.fingerprint===I)??-1:-1;if(m&&h&&y>=0){let[v]=h.splice(y,1);v.toolUseId=e.tool_use_id;let S=m.activeAskUserQuestionInvocation?.occurrenceId===v.occurrenceId;S&&(m.activeAskUserQuestionFingerprint&&(m.completedAskUserQuestionFingerprints||(m.completedAskUserQuestionFingerprints=new Set),m.completedAskUserQuestionFingerprints.add(m.activeAskUserQuestionFingerprint)),this.clearPromptState(m),v.completionEpoch=m.promptStateEpoch,m.activeAskUserQuestionInvocation=void 0);for(let E of v.e1PromptIds)await this.resolveE1PluginApproval(a,E);this.scheduleQueuedMobilePromptDrain(a),s.info("AskUserQuestion resolved by PostToolUse hook",{sessionId:a,occurrenceId:v.occurrenceId,toolUseId:v.toolUseId,promptIds:v.e1PromptIds,clearedCurrentPrompt:S})}else s.warn("Ignoring uncorrelated AskUserQuestion PostToolUse hook",{sessionId:a,toolUseId:e.tool_use_id,completionFingerprint:I?.slice(0,16)})}if(this.toolActivity?.isActive()&&(i===p.EventType.USER_PROMPT||i===p.EventType.ASSISTANT_RESPONSE||i===p.EventType.INTERACTIVE_PROMPT||i===p.EventType.NOTIFICATION)&&await this.toolActivity.flush().catch(m=>s.warn("[tool-activity] boundary flush failed",{sessionId:a,err:String(m)})),n==="UserPromptSubmit"){let m=this.activeSessions.get(a);if(m&&await this.clearInteractivePromptsForAcceptedInput(m),m?.completedAskUserQuestionFingerprints?.size){let I=m.completedAskUserQuestionFingerprints.size;m.completedAskUserQuestionFingerprints.clear(),s.info("Turn boundary \u2014 cleared closed-AskUserQuestion fingerprints",{sessionId:a,clearedCount:I})}}if(i===p.EventType.USER_PROMPT&&e.source===p.EventSource.DESKTOP&&n==="UserPromptSubmit"&&r&&this.isRecentMobilePrompt(a,r)){s.info("Skipping duplicate USER_PROMPT from mobile-originated prompt",{sessionId:a,contentLength:r.length});return}if(i===p.EventType.INTERACTIVE_PROMPT){(typeof e.prompt_id!="string"||e.prompt_id.length===0)&&(e.prompt_id=`synth-${(0,te.randomUUID)()}`,s.info("Synthesized prompt_id for INTERACTIVE_PROMPT (hook omitted it)",{sessionId:a,synthesizedPromptId:e.prompt_id}));let m=this.activeSessions.get(a),I;if(m&&e.metadata?.tool_name==="AskUserQuestion"){if(I=this.computeAskUserQuestionFingerprint(e.metadata.tool_input?.questions),I){let S=m.activeAskUserQuestionFingerprint===I,E=m.completedAskUserQuestionFingerprints?.has(I)??!1;if(S||E){s.info("Dropping duplicate INTERACTIVE_PROMPT \u2014 AskUserQuestion already tracked",{sessionId:a,fingerprint:I.slice(0,16),status:S?"in-flight":"completed",hookEvent:e.hook_event_name,promptId:e.prompt_id});return}}let h=e.metadata.tool_input?.questions,y=Array.isArray(h)&&h.some(S=>S?.multiSelect===!0),v=Array.isArray(h)&&(h[0]?.options?.length??0)===0;if(I&&(y||v)){m.completedAskUserQuestionFingerprints||(m.completedAskUserQuestionFingerprints=new Set),m.completedAskUserQuestionFingerprints.add(I);let S=y?"\u26A0\uFE0F This AskUserQuestion uses multi-select, which can't be answered from mobile. Please answer it on your desktop terminal.":"\u26A0\uFE0F This AskUserQuestion has no explicit options \u2014 please answer on your desktop terminal.";s.info("AUQ degraded at intercept \u2014 emitting notification, skipping walker setup",{sessionId:a,fingerprint:I.slice(0,16),hasMultiSelect:y,isZeroExplicitOption:v,questionCount:Array.isArray(h)?h.length:0}),setImmediate(()=>this.emitDegradedAUQNotification(a,S));let E=this.newE1Raise(a,!1),U=this.registerAskUserQuestionInvocation(m,I,this.nextPromptGen++,E.promptId);m.activeAskUserQuestionInvocation=U,this.emitSuppressedPromptCarrier(a,E).catch(M=>{s.error("E1: failed to emit degraded-AUQ badge carrier",{sessionId:a,promptId:E.promptId,error:M instanceof Error?M.message:String(M)})});return}}if(m){let h=this.newE1Raise(a,!0);e.prompt_id=h.promptId,this.clearPromptState(m),m.waitingForPromptResponse=!0,m.pendingPromptId=e.prompt_id;let y=this.nextPromptGen++;if(m.promptGenerationToken={promptId:e.prompt_id||"",gen:y},I){m.activeAskUserQuestionFingerprint=I;let v=this.registerAskUserQuestionInvocation(m,I,y,h.promptId);m.activeAskUserQuestionInvocation=v}s.info("Interactive prompt detected - will parse options from tmux",{sessionId:a,promptId:e.prompt_id,tokenGen:y,askUserQuestionFingerprint:I?.slice(0,16)})}this.sendInteractivePromptAsync(a,e,r).catch(h=>{s.error("Failed to send interactive prompt with dynamic options",{error:h})});return}if(i===p.EventType.TOOL_USE&&this.toolActivity?.isActive()&&await this.toolActivity.observeHookTool({toolUseId:e.tool_use_id,toolName:e.metadata?.tool_name,toolInput:e.tool_input})!=="closed")return;i===p.EventType.TOOL_USE&&e.tool_use_id&&this.sessionKey&&(c=String(e.tool_use_id),o=this.getToolActivityLegacyRecorder(a),o.markLegacyInFlight(c)),s.info("Hook event encryption state",{type:i,sessionId:a,hasSessionKey:!!this.sessionKey,sessionKeyLength:this.sessionKey?.length||0});let d=this.encryptForEmit(r,e.metadata,i,a);if(!d)return;let P=d.content,w=d.metadata,f=d.isEncrypted,g=await this.appSyncClient.createEvent({sessionId:a,type:i,source:e.source,content:P,metadata:w,promptId:e.prompt_id,timestamp:(0,p.prepareEventTimestamp)({orderingKey:a}),isEncrypted:f?!0:void 0});if(o&&c&&((0,p.isPersistedEventResult)(g)?await o.finalizeLegacyOwned([c]):o.clearLegacyInFlight(c)),i===p.EventType.USER_PROMPT&&e.source===p.EventSource.DESKTOP){let m=this.activeSessions.get(a);m?.waitingForPromptResponse&&(this.promoteFingerprintAndClearPromptState(m),s.info("Clearing prompt wait state - new desktop prompt received",{sessionId:a}))}s.debug("Event sent to AppSync successfully")}catch(a){throw o&&c&&o.clearLegacyInFlight(c),s.error("Failed to process hook event:",a),a}}buildToolActivityIntegration(){return new ye({logger:s,createEvent:e=>this.appSyncClient.createEvent(e),encryptContent:(e,t)=>p.cryptoService.encryptContent(e,t),encryptMetadata:(e,t)=>p.cryptoService.encryptMetadata(e,t),getSessionKey:()=>this.sessionKey,getSessionKeyGen:()=>this.sessionKeyGen,refreshSessionKey:e=>this.refreshToolActivitySessionKey(e),onTranscriptApiError:(e,t)=>this.relayTranscriptApiError(e,t)})}async relayTranscriptApiError(e,t){let n=nt(t),i={phase:"turn_error",notification_type:"api_error",claudeError:t.error??"unknown",transcriptUuid:t.uuid,...t.apiErrorStatus!==void 0?{apiErrorStatus:t.apiErrorStatus}:{},...t.apiErrorIsTransient!==void 0?{apiErrorIsTransient:t.apiErrorIsTransient}:{}},r=this.encryptForEmit(n,i,p.EventType.NOTIFICATION,e);return r?(await this.appSyncClient.createEvent({sessionId:e,type:p.EventType.NOTIFICATION,source:p.EventSource.DESKTOP,content:r.content,metadata:r.metadata,timestamp:(0,p.prepareEventTimestamp)({orderingKey:e}),isEncrypted:r.isEncrypted?!0:void 0}),s.info("Relayed Claude Code API error to mobile as NOTIFICATION",{sessionId:e,error:t.error??"unknown",apiErrorStatus:t.apiErrorStatus,transcriptUuid:t.uuid}),"delivered"):"dropped"}async refreshToolActivitySessionKey(e){try{let t=await this.appSyncClient.getSession(e);if(t&&t.status===p.SessionStatus.INACTIVE){let o=new Error(`SESSION_TERMINAL: session ${e} is ${t.status}; stop retrying`);throw o.errorType="SessionTerminal",o}if(!t||t.status!==p.SessionStatus.ACTIVE||!t.isEncrypted)return null;let n=t.sessionKeyGen??null,i=t.encryptedKeys??[];if(i.length===0)return null;let r=await p.keychainManager.getSessionKey(e,i,n);return r?(s.info("[tool-activity] refreshed session key for SessionKeyStale retry (live key untouched)",{sessionId:e,sessionKeyGen:n}),{sessionKey:r,sessionKeyGen:n}):null}catch(t){if((0,p.isTerminalSessionError)(t))throw t;return s.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 p.ToolActivityLedger({agent:"claude",sessionId:e,logger:s}),this.toolActivityLegacyRecorders.set(e,t)),t}handleSessionStart(e,t,n="explicit"){if(this.isStopping)return Promise.resolve();let i=e.session_id,r=n==="explicit"?this.recordExplicitClaudeSessionStart(i):{ownershipGeneration:this.claudeOwnershipGeneration,sessionIntentVersion:this.getClaudeSessionIntentVersion(i)};return this.enqueueSessionLifecycle(async()=>{if(this.isStopping)return;if(n==="self-heal"){if(!this.canSelfHealClaudeSession(i,r)){s.warn("Rejected stale Claude self-heal request",{claudeSessionId:i,currentClaudeSessionId:this.currentClaudeSessionId});return}this.currentClaudeSessionId===null&&(this.currentClaudeSessionId=i,r.ownershipGeneration=++this.claudeOwnershipGeneration)}else if(!this.isClaudeStartIntentCurrent(i,r))return;let o=++this.lifecycleGeneration;await this.handleSessionStartSerialized(e,t,o,()=>this.isClaudeStartIntentCurrent(i,r))})}getClaudeSessionIntentVersion(e){return this.claudeSessionIntentVersions.get(e)??0}bumpClaudeSessionIntentVersion(e){let t=this.getClaudeSessionIntentVersion(e)+1;return this.claudeSessionIntentVersions.set(e,t),t}recordExplicitClaudeSessionStart(e){this.clearEncryptedKeyRecovery("explicit Claude SessionStart");let t=this.currentClaudeSessionId;t&&t!==e&&this.endedOrSupersededClaudeSessionIds.add(t);for(let n of this.activeSessions.values())n.claudeSessionId!==e&&this.endedOrSupersededClaudeSessionIds.add(n.claudeSessionId);return this.currentClaudeSessionId=e,this.endedOrSupersededClaudeSessionIds.delete(e),{ownershipGeneration:++this.claudeOwnershipGeneration,sessionIntentVersion:this.bumpClaudeSessionIntentVersion(e)}}recordExplicitClaudeSessionEnd(e){this.clearEncryptedKeyRecovery("explicit Claude SessionEnd",e),this.endedOrSupersededClaudeSessionIds.add(e),this.bumpClaudeSessionIntentVersion(e),this.currentClaudeSessionId===e&&(this.currentClaudeSessionId=null,this.claudeOwnershipGeneration+=1)}isClaudeStartIntentCurrent(e,t){return!this.isStopping&&this.currentClaudeSessionId===e&&this.claudeOwnershipGeneration===t.ownershipGeneration&&this.getClaudeSessionIntentVersion(e)===t.sessionIntentVersion&&!this.endedOrSupersededClaudeSessionIds.has(e)}canSelfHealClaudeSession(e,t){return this.endedOrSupersededClaudeSessionIds.has(e)||this.getClaudeSessionIntentVersion(e)!==t.sessionIntentVersion||this.claudeOwnershipGeneration!==t.ownershipGeneration||this.currentClaudeSessionId&&this.currentClaudeSessionId!==e?!1:!Array.from(this.activeSessions.values()).some(n=>n.claudeSessionId!==e)}enqueueSessionLifecycle(e){let t=this.sessionStartChain;this.sessionLifecycleQueueDepth+=1;let n=this.sessionLifecycleQueueDepth===1?e():t.catch(()=>{}).then(e),i=n.finally(()=>{this.sessionLifecycleQueueDepth-=1});return this.sessionStartChain=i.catch(()=>{}),n}async handleSessionStartSerialized(e,t,n=this.lifecycleGeneration,i=()=>!0){let r=()=>this.isLifecycleCurrent(n)&&i();if(!r())return;let o=e.session_id,c=this.appSyncClient.getCurrentUserId(),a=this.claudeToBackendSessionId.get(o),l=t||a?null:await this.sessionBindingStore.read(c,o);if(!r())return;let d=t??a??l??this.generateBackendSessionId(o),P=e.metadata?.cwd||process.cwd();this.claudeToBackendSessionId.set(o,d),s.info("Session started",{claudeSessionId:o,sessionId:d,cwd:P});let w=this.activeSessions.get(d);if(w){if(w.mobileEndWatcher?.stop(),w.mobileEndWatcher=void 0,w.orchestrationPromptWatcher?.stop(),w.orchestrationPromptWatcher=void 0,w.orchestrationDecisionWatcher?.stop(),w.orchestrationDecisionWatcher=void 0,w.orchestrationClient){try{await w.orchestrationClient.destroy()}catch(h){s.warn("[v1-bridge] OrchestrationClient.destroy() failed on same-session restart",{sessionId:d,error:String(h)})}w.orchestrationClient=void 0}this.appSyncClient.stopHeartbeat(d),this.appSyncClient.cleanupSubscription(d),this.activeSessions.get(d)===w&&this.activeSessions.delete(d)}let f=Array.from(this.activeSessions.keys()).filter(h=>h!==d);if(f.length>0){s.info(`Marking ${f.length} previous session(s) as INACTIVE`);for(let h of f){if(this.toolActivity&&this.toolActivity.sessionId===h){let v=this.toolActivity,S=!1;for(let E=1;E<=2&&!S;E+=1)try{await v.stop(),S=!0}catch(U){s.warn("[tool-activity] outgoing consolidator drain failed on resume",{prevId:h,attempt:E,error:String(U)})}S||s.error("[api-error-relay] outgoing drain failed twice on resume \u2014 discarding; an uncheckpointed API error of the outgoing session may be lost (residual R7)",{prevId:h}),this.toolActivity=null}let y=this.activeSessions.get(h);if(y?.mobileEndWatcher&&(y.mobileEndWatcher.stop(),y.mobileEndWatcher=void 0),y?.orchestrationPromptWatcher&&(y.orchestrationPromptWatcher.stop(),y.orchestrationPromptWatcher=void 0),y?.orchestrationDecisionWatcher&&(y.orchestrationDecisionWatcher.stop(),y.orchestrationDecisionWatcher=void 0),y?.orchestrationClient){try{await y.orchestrationClient.destroy()}catch(v){s.warn("[v1-bridge] OrchestrationClient.destroy() failed on resume",{prevId:h,error:String(v)})}y.orchestrationClient=void 0}this.appSyncClient.stopHeartbeat(h),this.appSyncClient.cleanupSubscription(h);try{await this.appSyncClient.updateSession({sessionId:h,status:p.SessionStatus.INACTIVE}),s.info("Previous session marked INACTIVE",{prevId:h,newSessionId:d})}catch(v){s.warn("Failed to mark previous session as INACTIVE",{prevId:h,error:v})}y&&this.removePortFile(y.claudeSessionId),this.activeSessions.delete(h)}}this.writePortFile(o);let g={sessionId:d,claudeSessionId:o,userId:c,projectPath:P,cwd:P,createdAt:new Date,subscriptionActive:!1,waitingForPromptResponse:!1,promptStateEpoch:0,metadata:e.metadata||{},transcriptPath:e.transcript_path,originatedDecisions:new Set,externallyResolvedGateIds:new Set};this.encryptionResolved=!1,g.encryptionSnapshot={sessionKey:null,sessionIsEncrypted:!1,encryptionResolved:!1},this.activeSessions.set(d,g);try{let h;try{h=await(0,p.resumeOrCreateSession)({sessionId:d,userId:g.userId,agentType:p.AgentType.CLAUDE,projectPath:P,metadata:e.metadata||{}},this.appSyncClient,s)}catch(y){if(!(0,p.isRetiredSessionCannotReactivateError)(y))throw y;if(!r()||this.activeSessions.get(d)!==g){this.activeSessions.get(d)===g&&this.activeSessions.delete(d),this.claudeToBackendSessionId.get(o)===d&&this.claudeToBackendSessionId.delete(o),this.removePortFile(o);return}let v=d;this.appSyncClient.stopHeartbeat(v),this.appSyncClient.cleanupSubscription(v),this.activeSessions.delete(v);let S=ke(c,o,v);if(await this.sessionBindingStore.write(c,o,S),!r())return;d=S,g.sessionId=d,this.activeSessions.set(d,g),this.claudeToBackendSessionId.set(o,d),s.warn("Backend rejected retired Claude generation during SessionStart; creating replacement row",{claudeSessionId:o,retiredSessionId:v,replacementSessionId:d}),h=await(0,p.resumeOrCreateSession)({sessionId:d,userId:g.userId,agentType:p.AgentType.CLAUDE,projectPath:P,metadata:e.metadata||{}},this.appSyncClient,s)}if(!r()){await this.retireCancelledClaudeBootstrap({requestedSessionId:d,authoritativeSessionId:h.sessionId,claudeSessionId:o,sessionState:g});return}if(this.activeSessions.get(d)!==g){s.warn("Ignoring superseded Claude session bootstrap completion",{claudeSessionId:o,requestedSessionId:d,authoritativeSessionId:h.sessionId}),await this.retireCancelledClaudeBootstrap({requestedSessionId:d,authoritativeSessionId:h.sessionId,claudeSessionId:o,sessionState:g});return}if(h.sessionId!==d){if(await this.sessionBindingStore.write(c,o,h.sessionId),!r()){await this.retireCancelledClaudeBootstrap({requestedSessionId:d,authoritativeSessionId:h.sessionId,claudeSessionId:o,sessionState:g});return}this.activeSessions.get(d)===g&&this.activeSessions.delete(d),d=h.sessionId,g.sessionId=d,this.activeSessions.set(d,g),this.claudeToBackendSessionId.set(o,d),s.info("Bound Claude session to recovered backend identity",{claudeSessionId:o,sessionId:d})}if(this.sessionKey=h.sessionKey,this.sessionKeyGen=h.sessionKeyGen,this.sessionIsEncrypted=!!h.sessionKey,this.encryptionResolved=!0,g.encryptionSnapshot={sessionKey:h.sessionKey,sessionIsEncrypted:!!h.sessionKey,encryptionResolved:!0},this.toolActivity&&(await this.toolActivity.stop(),this.toolActivity=null,!r())){await this.retireCancelledClaudeBootstrap({requestedSessionId:d,authoritativeSessionId:h.sessionId,claudeSessionId:o,sessionState:g});return}if(this.sessionKey){let y=this.buildToolActivityIntegration();try{if(await y.start(d,e.transcript_path,{hostingSinceMs:e.received_at_ms}),!r()){await y.stop().catch(()=>{}),await this.retireCancelledClaudeBootstrap({requestedSessionId:d,authoritativeSessionId:h.sessionId,claudeSessionId:o,sessionState:g});return}this.toolActivity=y}catch(v){s.warn("[tool-activity] consolidator start failed \u2014 keeping per-tool TOOL_USE",{sessionId:d,error:String(v)}),this.toolActivity=null,await y.stop().catch(()=>{})}}}catch(h){if(!r()){await this.retireCancelledClaudeBootstrap({requestedSessionId:d,authoritativeSessionId:this.authoritativeSessionIdFromError(h)??d,claudeSessionId:o,sessionState:g});return}let y=h?.authoritativeSessionId;if(typeof y=="string"&&y.length>0&&y!==d&&this.activeSessions.get(d)===g&&(this.activeSessions.delete(d),d=y,g.sessionId=d,this.activeSessions.set(d,g),this.claudeToBackendSessionId.set(o,d),s.info("Bound keyless Claude recovery to authoritative backend identity",{claudeSessionId:o,sessionId:d})),this.isSessionLimitExceeded(h)){this.displaySubscriptionLimitError(h,"session"),this.activeSessions.get(d)===g&&this.activeSessions.delete(d),this.removePortFile(o);return}if(h?.code==="ENCRYPTED_SESSION_NO_KEY"){this.sessionIsEncrypted=!0,this.sessionKey=null,this.encryptionResolved=!0,g.encryptionSnapshot={sessionKey:null,sessionIsEncrypted:!0,encryptionResolved:!0};let v=this.enterEncryptedKeyRecovery(e,d);this.appSyncClient.cleanupSubscription(d),await this.invalidateEncryptedRecoveryLiveness(v),s.error("Failed to create/resume session:",h);return}else{this.activeSessions.get(d)===g&&this.activeSessions.delete(d),s.error("Failed to create/resume session \u2014 entry removed so the next hook event re-runs bootstrap via self-heal (#638 G1):",h);return}}if(!r()){await this.retireCancelledClaudeBootstrap({requestedSessionId:d,authoritativeSessionId:d,claudeSessionId:o,sessionState:g});return}try{await this.recoverQueuedMobilePrompts(d)}catch(h){s.error("Durable mobile-prompt queue recovery failed closed",{sessionId:d,error:String(h)}),await this.sendPromptError(d,"A previously queued mobile message could not be recovered safely. Please inspect the desktop before retrying it.").catch(()=>{}),this.activeSessions.get(d)===g&&this.activeSessions.delete(d);return}this.subscribeToMobileEvents(d);try{await this.drainParkedE1Orphans(o,d,r)}catch(h){s.warn("E1: parked-orphan drain threw (non-fatal; TTL drain will retry)",{sessionId:d,error:h instanceof Error?h.message:String(h)})}if(!await this.appSyncClient.startHeartbeat(d)){s.error("Initial Claude session heartbeat was not persisted",{sessionId:d});let h=this.encryptedKeyRecovery;if(h?.claudeSessionId===o&&h.backendSessionId===d){let y=this.enterEncryptedKeyRecovery(e,d);this.appSyncClient.cleanupSubscription(d),await this.invalidateEncryptedRecoveryLiveness(y);return}}if(!r()){await this.retireCancelledClaudeBootstrap({requestedSessionId:d,authoritativeSessionId:d,claudeSessionId:o,sessionState:g});return}let I=this.activeSessions.get(d);if(I){I.mobileEndWatcher=this.appSyncClient.watchForMobileEnd(d,async()=>{s.info("Mobile ended session \u2014 sending desktop quit",{sessionId:d}),this.appSyncClient.stopHeartbeat(d),this.appSyncClient.cleanupSubscription(d);try{await this.appSyncClient.updateSession({sessionId:d,status:p.SessionStatus.INACTIVE}),s.info("Marked session INACTIVE on mobile-end (before tmux terminate)",{sessionId:d})}catch(v){s.warn("Failed to mark session INACTIVE on mobile-end",{sessionId:d,error:v})}let y=process.env[j];if(!y){s.warn("No tmux session set; skipping desktop self-terminate",{sessionId:d,expectedEnv:j});return}await jt(y,$t)});let h=!1;try{I.orchestrationPromptWatcher=this.subscribeToOrchestrationPrompts(d)}catch(y){h=!0,s.warn("[v1-bridge] Failed to start orchestration prompt watcher",{sessionId:d,error:String(y)})}try{I.orchestrationDecisionWatcher=await this.subscribeToApplyUserDecision(I)}catch(y){h=!0,s.warn("[v1-bridge] Failed to start applyUserDecision watcher",{sessionId:d,error:String(y)})}h&&this.emitBridgeUnavailableBanner()}this.clearEncryptedKeyRecovery("session key recovered and liveness established",o,d)}recoverRetiredBackendSession(e,t){let n=this.retiredSessionRecoveryPromises.get(e);if(n)return n;if(this.isStopping)return Promise.resolve();let i=t.claudeSessionId,r={ownershipGeneration:this.claudeOwnershipGeneration,sessionIntentVersion:this.getClaudeSessionIntentVersion(i)},o=()=>this.isClaudeStartIntentCurrent(i,r),a=this.enqueueSessionLifecycle(async()=>{if(this.isStopping||!o()||this.activeSessions.get(e)!==t||this.claudeToBackendSessionId.get(t.claudeSessionId)!==e)return;let l={session_id:t.claudeSessionId,metadata:t.metadata,transcript_path:t.transcriptPath};s.warn("Backend retired live Claude session; creating replacement row",{sessionId:e,claudeSessionId:t.claudeSessionId}),this.parkE1Orphans(t.claudeSessionId,this.snapshotOpenE1(e));let d=ke(t.userId,t.claudeSessionId,e);if(await this.sessionBindingStore.write(t.userId,t.claudeSessionId,d),!o())return;let P=++this.lifecycleGeneration;await this.handleSessionEndSerialized({session_id:t.claudeSessionId,metadata:{reason:"backend-generation-retired"}}),!(!this.isLifecycleCurrent(P)||!o())&&await this.handleSessionStartSerialized(l,d,P,o)}).finally(()=>{this.retiredSessionRecoveryPromises.get(e)===a&&this.retiredSessionRecoveryPromises.delete(e)});return this.retiredSessionRecoveryPromises.set(e,a),a}isLifecycleCurrent(e){return!this.isStopping&&this.lifecycleGeneration===e}authoritativeSessionIdFromError(e){let t=e?.authoritativeSessionId;return typeof t=="string"&&t.length>0?t:null}async retireCancelledClaudeBootstrap(e){let t=new Set([e.requestedSessionId,e.authoritativeSessionId]);for(let n of t)this.appSyncClient.stopHeartbeat(n),this.appSyncClient.cleanupSubscription(n);this.activeSessions.get(e.requestedSessionId)===e.sessionState&&this.activeSessions.delete(e.requestedSessionId),this.activeSessions.get(e.authoritativeSessionId)===e.sessionState&&this.activeSessions.delete(e.authoritativeSessionId),this.claudeToBackendSessionId.get(e.claudeSessionId)===e.requestedSessionId&&this.claudeToBackendSessionId.delete(e.claudeSessionId),this.removePortFile(e.claudeSessionId);try{await this.appSyncClient.updateSession({sessionId:e.authoritativeSessionId,status:p.SessionStatus.INACTIVE}),s.info("Cancelled Claude bootstrap row marked INACTIVE",{sessionId:e.authoritativeSessionId,claudeSessionId:e.claudeSessionId})}catch(n){s.warn("Cancelled Claude bootstrap row could not be marked INACTIVE",{sessionId:e.authoritativeSessionId,error:n instanceof Error?n.message:String(n)})}}async emitBridgeUnavailableBanner(){let e=process.env[j],t="[CodeVibe] Cross-device decision sync unavailable \u2014 mobile responses will be sent as free-text";if(s.warn(t),!!e)try{await we(`tmux display-message -t "${e}" -- ${JSON.stringify(t)}`)}catch(n){s.warn("[v1-bridge] tmux display-message for bridge-unavailable banner failed",{tmuxSession:e,error:String(n)})}}handleSessionEnd(e){return this.isStopping?Promise.resolve():(this.recordExplicitClaudeSessionEnd(e.session_id),this.enqueueSessionLifecycle(()=>this.handleSessionEndSerialized(e)))}async handleSessionEndSerialized(e){let t=e.session_id,n=this.claudeToBackendSessionId.get(t)||this.generateBackendSessionId(t);s.info("Session ended",{claudeSessionId:t,sessionId:n,reason:e.metadata?.reason});let i=this.activeSessions.get(n);if(i?.mobileEndWatcher&&(i.mobileEndWatcher.stop(),i.mobileEndWatcher=void 0),i?.orchestrationPromptWatcher&&(i.orchestrationPromptWatcher.stop(),i.orchestrationPromptWatcher=void 0),i?.orchestrationDecisionWatcher&&(i.orchestrationDecisionWatcher.stop(),i.orchestrationDecisionWatcher=void 0),i?.orchestrationClient){try{await i.orchestrationClient.destroy()}catch(c){s.warn("[v1-bridge] OrchestrationClient.destroy() failed",{sessionId:n,error:String(c)})}i.orchestrationClient=void 0}this.toolActivity&&this.toolActivity.sessionId===n&&(await this.toolActivity.stop(),this.toolActivity=null),this.activeSessions.delete(n),this.claudeToBackendSessionId.delete(t),this.removePortFile(t),i?.waitingForPromptResponse&&(s.info("Clearing prompt wait state - session ending",{sessionId:n}),this.clearPromptState(i));let r=this.queuedMobilePromptTimers.get(n);r&&clearTimeout(r),this.queuedMobilePromptTimers.delete(n),this.queuedMobilePrompts.delete(n);let o=this.toolActivityLegacyRecorders.get(n);if(o&&o.disposeLegacyCoordination(),this.toolActivityLegacyRecorders.delete(n),this.appSyncClient.stopHeartbeat(n),this.appSyncClient.cleanupSubscription(n),i)try{await this.appSyncClient.updateSession({sessionId:n,status:p.SessionStatus.INACTIVE}),s.info("Session marked as INACTIVE in AppSync",{sessionId:n})}catch(c){s.warn("Failed to update session in AppSync:",c)}else s.warn("Cannot update session - session state not found",{sessionId:n});s.debug("Session cleanup completed",{sessionId:n})}async withMobilePromptAdmission(e,t){let n,r=(this.queuedMobilePromptAdmissions.get(e)??Promise.resolve()).catch(()=>{}).then(async()=>{n=await t()});this.queuedMobilePromptAdmissions.set(e,r);try{return await r,n}finally{this.queuedMobilePromptAdmissions.get(e)===r&&this.queuedMobilePromptAdmissions.delete(e)}}recoverQueuedMobilePrompts(e){let t=this.activeSessions.get(e);if(!t)throw new Error(`Cannot recover mobile queue for inactive session ${e}`);return this.withMobilePromptAdmission(t.claudeSessionId,()=>this.recoverQueuedMobilePromptsOnce(e,t.claudeSessionId))}async recoverQueuedMobilePromptsOnce(e,t){let n=this.claudeToBackendSessionId.get(t)??e,i=this.activeSessions.get(n);if(!i||i.claudeSessionId!==t)throw new Error(`Cannot recover mobile queue for inactive native session ${t}`);let r=await this.mobilePromptQueueStore.read(t);for(let l of r)l.sessionId!==n&&(l.sessionId=n,await this.mobilePromptQueueStore.put(t,l)),l.state==="sending"&&(l.state="terminal",l.lastError="CodeVibe restarted while sending this message. It was not retried to avoid submitting it twice.",await this.mobilePromptQueueStore.put(t,l));let o=this.queuedMobilePrompts.get(n)??[],c=new Map;for(let l of[...r,...o])c.set(l.event.eventId,l);let a=[...c.values()];if(a.length>0){this.queuedMobilePrompts.set(n,a),i.processedEventIds||(i.processedEventIds=new Set);for(let l of a)i.processedEventIds.add(l.event.eventId);this.scheduleQueuedMobilePromptDrain(n)}}async enqueueMobilePrompt(e,t,n,i){let r=i??this.activeSessions.get(e)?.claudeSessionId;if(!r)throw new Error(`Cannot identify native mobile queue for ${e}`);return this.withMobilePromptAdmission(r,()=>this.enqueueMobilePromptOnce(e,r,t,n))}async enqueueMobilePromptOnce(e,t,n,i){let r=this.claudeToBackendSessionId.get(t)??e,o=this.activeSessions.get(r),c=this.queuedMobilePrompts.get(r)??await this.mobilePromptQueueStore.read(t),a=c.find(d=>d.event.eventId===n.eventId);if(a){let d=a.sessionId!==r;return a.sessionId=r,i&&!a.pendingDismissal&&(a.pendingDismissal=i,d=!0),d&&await this.mobilePromptQueueStore.put(t,a),o&&(o.processedEventIds||(o.processedEventIds=new Set),o.processedEventIds.add(n.eventId),this.queuedMobilePrompts.set(r,c),i||this.scheduleQueuedMobilePromptDrain(r)),a}let l={version:2,nativeSessionId:t,sessionId:r,event:n,state:"queued",attempts:0,nextAttemptAt:Date.now(),pendingDismissal:i};return await this.mobilePromptQueueStore.put(t,l),c.push(l),o&&this.activeSessions.get(r)===o&&(this.queuedMobilePrompts.set(r,c),o.processedEventIds||(o.processedEventIds=new Set),o.processedEventIds.add(n.eventId),i||this.scheduleQueuedMobilePromptDrain(r)),l}scheduleQueuedMobilePromptDrain(e,t=0){let n=this.queuedMobilePromptTimers.get(e);n&&clearTimeout(n);let i=setTimeout(()=>{this.queuedMobilePromptTimers.delete(e),this.drainQueuedMobilePrompts(e)},Math.max(0,t));i.unref?.(),this.queuedMobilePromptTimers.set(e,i)}queuedPromptBackoffMs(e){return Math.min(u.MOBILE_PROMPT_QUEUE_MAX_BACKOFF_MS,500*2**Math.max(0,e-1))}async terminalizeQueuedMobilePrompt(e,t){try{return t.terminalNoticeSent||(await this.sendPromptError(e.sessionId,t.lastError??"The queued message could not be sent. Please retry it."),t.terminalNoticeSent=!0,await this.mobilePromptQueueStore.put(t.nativeSessionId,t)),await this.acknowledgeQueuedMobilePromptExecuted(e,t)?(await this.mobilePromptQueueStore.remove(t.nativeSessionId,t.event.eventId),!0):!1}catch(n){return s.warn("Failed to publish queued mobile-prompt terminal disposition",{sessionId:e.sessionId,eventId:t.event.eventId,error:String(n)}),!1}}async acknowledgeQueuedMobilePromptExecuted(e,t){try{let n=await this.appSyncClient.updateEventStatus({eventId:t.event.eventId,sessionId:t.event.sessionId,timestamp:t.event.timestamp,deliveryStatus:p.DeliveryStatus.EXECUTED});return n?.eventId===t.event.eventId&&n?.sessionId===t.event.sessionId&&n?.timestamp===t.event.timestamp&&n?.deliveryStatus===p.DeliveryStatus.EXECUTED?(e.processedEventIds||(e.processedEventIds=new Set),e.processedEventIds.add(t.event.eventId),!0):(s.warn("Queued mobile-prompt EXECUTED acknowledgement was not exact; retaining tombstone",{sessionId:e.sessionId,sourceSessionId:t.event.sessionId,eventId:t.event.eventId,acknowledgedEventId:n?.eventId,acknowledgedSessionId:n?.sessionId,acknowledgedTimestamp:n?.timestamp,acknowledgedDeliveryStatus:n?.deliveryStatus}),!1)}catch(n){return s.warn("Queued mobile-prompt EXECUTED acknowledgement failed; retaining tombstone",{sessionId:e.sessionId,sourceSessionId:t.event.sessionId,eventId:t.event.eventId,state:t.state,error:String(n)}),!1}}async reconcileQueuedPromptDismissal(e,t){let n=t.pendingDismissal;if(!n)return"resolved";let i=n.invocation.occurrenceId,r=e.pendingAskUserQuestionInvocations?.some(c=>c.occurrenceId===i)??!1,o=e.activeAskUserQuestionInvocation?.occurrenceId;return!r&&o!==i?(n.dismissalSent&&await this.settleDismissedAskUserQuestionOccurrence(e,n.invocation),t.pendingDismissal=void 0,await this.mobilePromptQueueStore.put(t.nativeSessionId,t),"resolved"):o!==i||e.pendingPanePromptIdentity?.hookOccurrenceId!==i?(o&&o!==i&&await this.settleDismissedAskUserQuestionOccurrence(e,n.invocation),t.pendingDismissal=void 0,await this.mobilePromptQueueStore.put(t.nativeSessionId,t),"resolved"):n.nextAttemptAt>Date.now()?(this.scheduleQueuedMobilePromptDrain(e.sessionId,n.nextAttemptAt-Date.now()),"retry"):await this.resolveAskUserQuestionAfterVerifiedDismissal(e,n.expectedIdentity,n.invocation,{attempts:1,delayMs:0})?(t.pendingDismissal=void 0,await this.mobilePromptQueueStore.put(t.nativeSessionId,t),"resolved"):(n.attempts+=1,n.attempts>=u.MOBILE_PROMPT_QUEUE_MAX_ATTEMPTS?(t.pendingDismissal=void 0,t.state="terminal",t.lastError="The desktop chooser did not close after dismissal, so this message was not submitted. Please retry it.",await this.mobilePromptQueueStore.put(t.nativeSessionId,t),"terminal"):(n.nextAttemptAt=Date.now()+this.queuedPromptBackoffMs(n.attempts),t.lastError="Waiting for the dismissed desktop chooser to close",await this.mobilePromptQueueStore.put(t.nativeSessionId,t),this.scheduleQueuedMobilePromptDrain(e.sessionId,n.nextAttemptAt-Date.now()),"retry"))}async drainQueuedMobilePrompts(e){if(this.queuedMobilePromptDrains.has(e))return;let t=this.activeSessions.get(e),n=this.queuedMobilePrompts.get(e);if(!(!t||!n?.length)){this.queuedMobilePromptDrains.add(e);try{for(;n.length>0;){if(this.activeSessions.get(e)!==t)return;let i=n[0];if(i.state==="submitted"){if(!await this.acknowledgeQueuedMobilePromptExecuted(t,i)){this.scheduleQueuedMobilePromptDrain(e,2e3);return}await this.mobilePromptQueueStore.remove(i.nativeSessionId,i.event.eventId),n.shift();continue}if(i.state==="terminal"){if(!await this.terminalizeQueuedMobilePrompt(t,i)){this.scheduleQueuedMobilePromptDrain(e,2e3);return}n.shift();continue}let r=await this.reconcileQueuedPromptDismissal(t,i);if(r==="retry")return;if(r==="terminal")continue;if(t.activeAskUserQuestionInvocation||t.waitingForPromptResponse||t.promptGenerationToken!==void 0){this.scheduleQueuedMobilePromptDrain(e,500);return}let o=i.nextAttemptAt-Date.now();if(o>0){this.scheduleQueuedMobilePromptDrain(e,o);return}if(!await this.waitForChooserAbsence(e,t,{attempts:1,delayMs:0})){i.attempts+=1,i.attempts>=u.MOBILE_PROMPT_QUEUE_MAX_ATTEMPTS?(i.state="terminal",i.lastError="The desktop chooser did not close, so this message was not submitted. Please retry it."):(i.nextAttemptAt=Date.now()+this.queuedPromptBackoffMs(i.attempts),i.lastError="Waiting for the desktop chooser to close"),await this.mobilePromptQueueStore.put(i.nativeSessionId,i);continue}if(i.state="sending",i.lastError=void 0,await this.mobilePromptQueueStore.put(i.nativeSessionId,i),await this.executeMobilePrompt(e,i.event,{suppressFailureNotification:!0,deferStatusUpdate:!0})){i.state="submitted",await this.mobilePromptQueueStore.put(i.nativeSessionId,i);continue}i.state="terminal",i.lastError="CodeVibe could not prove whether this message reached Claude. It was not retried to avoid submitting it twice.",await this.mobilePromptQueueStore.put(i.nativeSessionId,i)}this.queuedMobilePrompts.delete(e)}finally{this.queuedMobilePromptDrains.delete(e),n.length>0&&!this.queuedMobilePromptTimers.has(e)&&this.scheduleQueuedMobilePromptDrain(e,500)}}}subscribeToMobileEvents(e){s.info("Subscribing to mobile events",{sessionId:e});let t=this.activeSessions.get(e);if(!t){s.error("Session not found",{sessionId:e});return}this.appSyncClient.subscribeToEvents(e,async n=>this.dispatchMobileEvent(e,n),n=>{s.error("Subscription error",{sessionId:e,error:n})},{onSessionRetired:()=>this.recoverRetiredBackendSession(e,t)}),t.subscriptionActive=!0,s.info("Subscription active",{sessionId:e})}async dispatchMobileEvent(e,t){s.info("Received mobile event",{eventId:t.eventId,type:t.type,sessionId:t.sessionId,isEncrypted:t.isEncrypted});let n=t.content||"";if(t.isEncrypted&&this.sessionKey)try{n=p.cryptoService.decryptContent(t.content,this.sessionKey),s.debug("Event decrypted successfully",{eventId:t.eventId})}catch(g){s.error("Failed to decrypt event:",{eventId:t.eventId,error:g}),n=t.content}let i,r,o=!1,c,a,l,d,P;if(t.type===p.EventType.USER_PROMPT||t.type===p.EventType.PROMPT_RESPONSE)if(r=this.activeSessions.get(e),P=(0,p.classifyMobilePromptInput)(n,Object.keys(r?.pendingSubmitMap??{})),!r)i="no-session";else if(r.processedEventIds?.has(t.eventId))i="skip-dedup";else if(r.inFlightEventIds?.has(t.eventId))i="drop-event-redeliver";else if(r.waitingForPromptResponse){let g=r.promptGenerationToken;if(!g)i="regular";else if(t.type===p.EventType.USER_PROMPT&&P.kind==="option"&&r.hasReceivedPromptResponse&&(!t.promptId||t.promptId.length===0))i="drop-stale-answer";else if(P.kind==="option"&&t.type===p.EventType.PROMPT_RESPONSE&&(!t.promptId||g.promptId.length===0||t.promptId!==g.promptId))i="drop-stale-answer";else if(P.kind==="option"&&t.promptId&&g.promptId.length>0&&t.promptId!==g.promptId)i="drop-stale-answer";else{let m=g.promptId.length>0?g.promptId:`__prompt_gen_${g.gen}`;r.inFlightPromptIds?.has(m)?i="drop-in-flight":(r.inFlightPromptIds||(r.inFlightPromptIds=new Set),r.inFlightEventIds||(r.inFlightEventIds=new Set),r.inFlightPromptIds.add(m),r.inFlightEventIds.add(t.eventId),o=!0,l=m,d=t.eventId,c={promptId:g.promptId,gen:g.gen},a={pendingPromptId:r.pendingPromptId,pendingSubmitMap:r.pendingSubmitMap?{...r.pendingSubmitMap}:void 0,pendingPanePromptIdentity:r.pendingPanePromptIdentity,activeAskUserQuestionInvocation:r.activeAskUserQuestionInvocation},i="walker")}}else i="regular";else i="not-user-prompt";let f={...t,content:n};if(i!=="skip-dedup")try{await this.appSyncClient.updateEventStatus({eventId:t.eventId,sessionId:t.sessionId,timestamp:t.timestamp,deliveryStatus:p.DeliveryStatus.DELIVERED}),s.info("Event marked as DELIVERED",{eventId:t.eventId})}catch(g){s.warn("Failed to mark event as DELIVERED",{eventId:t.eventId,error:g})}if(t.type===p.EventType.USER_PROMPT&&r?.pendingOrchestrationGate&&await this.handleOrchestrationPromptResponse(r,n,t)){l&&r.inFlightPromptIds?.delete(l),d&&r.inFlightEventIds?.delete(d);return}if(i==="skip-dedup"){s.info("[walker] Subscription-level dedup hit (already processed) \u2014 skipping",{sessionId:e,eventId:t.eventId});return}if(i==="drop-stale-answer"){s.info("[walker] Stale answer dropped \u2014 event.promptId does not match current pending promptId",{sessionId:e,eventId:t.eventId,eventPromptId:t.promptId,currentPromptId:r?.promptGenerationToken?.promptId}),r&&(r.processedEventIds||(r.processedEventIds=new Set),r.processedEventIds.add(t.eventId));try{await this.markEventExecuted(t)}catch(g){s.warn("[walker] markEventExecuted threw on stale-answer drop \u2014 relying on processedEventIds Set",{sessionId:e,eventId:t.eventId,error:String(g)})}return}if(i==="drop-in-flight"){s.warn("[walker] Subscription-level in-flight guard \u2014 dropping duplicate USER_PROMPT (different eventId, same prompt)",{sessionId:e,eventId:t.eventId}),r&&(r.processedEventIds||(r.processedEventIds=new Set),r.processedEventIds.add(t.eventId));try{await this.markEventExecuted(t)}catch(g){s.warn("[walker] markEventExecuted threw on subscription-level duplicate drop \u2014 relying on processedEventIds Set",{sessionId:e,eventId:t.eventId,error:String(g)})}return}if(i==="drop-event-redeliver"){s.info("[walker] Subscription-level event-level redelivery \u2014 silent skip (original still in flight)",{sessionId:e,eventId:t.eventId});return}if(i==="walker"){t.type===p.EventType.PROMPT_RESPONSE&&P?.kind==="option"&&r&&(r.hasReceivedPromptResponse=!0),await this.handleMobilePromptResponse(e,t,n,r,o,c,l,d,a);return}if(i==="regular"){if(t.type===p.EventType.PROMPT_RESPONSE&&P?.kind==="option"){s.warn("Received PROMPT_RESPONSE with no active walker \u2014 dropping",{sessionId:e,eventId:t.eventId,promptId:t.promptId}),r&&(r.processedEventIds||(r.processedEventIds=new Set),r.processedEventIds.add(t.eventId));try{await this.markEventExecuted(t)}catch(g){s.warn("markEventExecuted threw on PROMPT_RESPONSE orphan drop \u2014 relying on processedEventIds Set",{sessionId:e,eventId:t.eventId,error:String(g)})}return}await this.executeMobilePrompt(e,f);return}if(i==="no-session"){s.warn("Received mobile prompt input for unknown session \u2014 ignoring",{sessionId:e,eventId:t.eventId,type:t.type});return}}async handleMobilePromptResponse(e,t,n,i,r=!1,o,c,a,l){let d=o??i.promptGenerationToken,P=c,w=a;if(!r&&d){let f=d.promptId.length>0?d.promptId:`__prompt_gen_${d.gen}`;if(i.inFlightPromptIds?.has(f)){s.warn("[walker] Duplicate mobile USER_PROMPT for same prompt \u2014 dropping",{sessionId:e,eventId:t.eventId,lockKey:f}),await this.markEventExecutedIdempotent(i,t);return}i.inFlightPromptIds||(i.inFlightPromptIds=new Set),i.inFlightEventIds||(i.inFlightEventIds=new Set),i.inFlightPromptIds.add(f),i.inFlightEventIds.add(t.eventId),P=f,w=t.eventId}try{if(!r&&i.processedEventIds?.has(t.eventId)){s.info("[walker] Redelivered event already processed \u2014 skipping",{sessionId:e,eventId:t.eventId});return}let f=n.trim(),g=l?l.pendingPromptId:i.pendingPromptId,m=l?l.pendingSubmitMap:i.pendingSubmitMap,I=l?l.pendingPanePromptIdentity:i.pendingPanePromptIdentity,h=l?l.activeAskUserQuestionInvocation:i.activeAskUserQuestionInvocation,y=m,v=this.parseInteractivePromptInput(f,Object.keys(y??{}));if(s.info("Parsed interactive prompt input",{sessionId:e,content:f,parsed:v,hasSubmitMap:!!y}),v.action==="send_as_response"&&I?.kind==="ask_user_question"){let U=i.promptGenerationToken,M=this.activeSessions.get(e)===i&&U?.gen===d?.gen&&U?.promptId===d?.promptId,b=h,A=M&&b?{expectedIdentity:I,invocation:b,attempts:0,nextAttemptAt:Date.now()}:void 0,O=await this.enqueueMobilePrompt(e,{...t,content:n},A,i.claudeSessionId);if(!M||!b){this.scheduleQueuedMobilePromptDrain(e);return}let _=await this.dismissLivePromptForConversation(i,d);_&&O.pendingDismissal&&(O.pendingDismissal.dismissalSent=!0,await this.mobilePromptQueueStore.put(O.nativeSessionId,O));let Q=_&&b&&await this.resolveAskUserQuestionAfterVerifiedDismissal(i,I,b);Q&&O.pendingDismissal&&(O.pendingDismissal=void 0,await this.mobilePromptQueueStore.put(O.nativeSessionId,O)),this.scheduleQueuedMobilePromptDrain(e),Q||await this.sendPromptError(e,"The active prompt could not be dismissed yet; your message is safely queued");return}let S=d&&h?{token:{...d},invocation:h}:void 0,E=()=>{if(S&&this.isExpectedAskUserQuestionTransitionCurrent(e,i,S))return!1;let U=i.promptGenerationToken,M=U?.gen,b=d?.gen;return this.activeSessions.get(e)!==i||M!==b?(s.warn("[walker] Token mismatch \u2014 external cleanup or new prompt during in-flight handler \u2014 aborting",{sessionId:e,eventId:t.eventId,entryToken:d,currentToken:U}),!0):!1};if(E()){await this.markEventExecutedIdempotent(i,t);return}if(v.action==="select_option"){let U=y?.[v.option]||v.option,M=i.pendingQuestionsQueue!==void 0,b=i.pendingCurrentQuestion?.options?.length??0,A=v.option?parseInt(v.option,10):NaN,O=i.pendingSynthTail?.length??0;if(M&&i.pendingCurrentQuestion!==void 0&&O>0&&Number.isFinite(A)&&A>b&&A<=b+O){if(await this.markEventExecutedIdempotent(i,t),E())return;let T=await this.sendVerifiedPromptKey(i,I,"Escape");if(E())return;if(!(T&&I?.kind==="ask_user_question"&&h&&await this.resolveAskUserQuestionAfterVerifiedDismissal(i,I,h))){await this.sendPromptError(e,"The active prompt could not be dismissed; please retry");return}await this.emitUserChoice(e,"\u2192 AskUserQuestion dismissed; enter your reply as a new prompt");return}s.info("User selected option",{option:v.option,terminalInput:U,isV2AskUserQuestion:M});let Q=await this.answerVerifiedInteractivePrompt(i,I,U,{pressEnter:!M});if(E()){await this.markEventExecutedIdempotent(i,t);return}if(Q){if(await this.markEventExecutedIdempotent(i,t),E())return;if(!g){s.warn("emitAnswerAck called without promptId \u2014 clearing state + skipping ack",{sessionId:e,source:"select_option",eventId:t.eventId}),this.promoteFingerprintAndClearPromptState(i);return}let T=(i.pendingQuestionsQueue?.length??0)===0;try{if(M){let G=parseInt(v.option,10)-1,X=i.pendingCurrentQuestion?.options?.[G],ee=typeof X=="string"?X:X&&typeof X=="object"?X.label:`option ${v.option}`,Ue=i.pendingCurrentQuestion?._isSubmit===!0,Me=ee.toLowerCase(),ae;Ue&&Me==="cancel"?ae="\u2192 Cancel \u2014 AskUserQuestion cancelled, no answers submitted":Ue&&Me.startsWith("submit")?ae="\u2192 Submit answers \u2014 AskUserQuestion completed":ae=`\u2192 ${ee}`,await this.emitUserChoice(e,ae)}else await this.emitAnswerAck(e,`Selected option ${v.option}`,{promptId:g,questionIndex:0,isTerminal:T})}catch(G){s.warn("[walker] user-choice/ack emit failed \u2014 continuing to STEP 7/8",{sessionId:e,promptId:g,isV2AskUserQuestion:M,error:G instanceof Error?G.message:String(G)})}if(E())return;let k=i.pendingQuestionsQueue?.shift();if(k&&(i.pendingCurrentQuestion=k,i.pendingPanePromptIdentity=this.buildAskUserQuestionPaneIdentity(k,i.activeAskUserQuestionInvocation?.occurrenceId)),!k){i.activeAskUserQuestionFingerprint&&(i.completedAskUserQuestionFingerprints||(i.completedAskUserQuestionFingerprints=new Set),i.completedAskUserQuestionFingerprints.add(i.activeAskUserQuestionFingerprint),s.info("AskUserQuestion V2 walker complete \u2014 fingerprint marked closed",{sessionId:e,fingerprint:i.activeAskUserQuestionFingerprint.slice(0,16)})),this.clearPromptState(i);return}let H=this.newE1Raise(e,!0),F=H.promptId;if(i.activeAskUserQuestionInvocation?.e1PromptIds.push(F),!F){this.promoteFingerprintAndClearPromptState(i),s.warn("Q[next] emit aborted: synthesized promptId was empty; promoted fingerprint + cleared prompt state",{sessionId:e,eventId:t.eventId});return}let q=i.pendingSynthTail??[],z=this.buildQuestionWireData(k,q),J=k.question,Y={tool_name:"AskUserQuestion",tool_input:{questions:[k]},options:z.options,submitMap:z.submitMap,instructions:z.instructions},W=i.encryptionSnapshot,x=(0,p.encryptForEmit)(W?.sessionKey??null,W?.sessionIsEncrypted??!1,J,Y,W?W.encryptionResolved:!1);if(!x){s.error("No session key for ENCRYPTED session \u2014 dropping Q[next] prompt (fail-closed, #638)",{sessionId:e,promptId:F}),this.promoteFingerprintAndClearPromptState(i);return}let C=x.content,Z=x.metadata,yt=x.isEncrypted,vt=this.nextPromptGen++,Se={promptId:F,gen:vt};i.pendingPromptId=F,i.pendingSubmitMap=z.submitMap,i.promptGenerationToken=Se;let ie=Se,oe=this.activeSessions.get(e)?.promptGenerationToken;if(!oe||oe.gen!==ie.gen||oe.promptId!==ie.promptId){s.warn("Q[next] emit aborted: token replaced before await dispatch",{sessionId:e,tokenAtAwait:ie,currentToken:oe});return}let xe=F.length>0?F:`__prompt_gen_${Se.gen}`;i.inFlightPromptIds||(i.inFlightPromptIds=new Set),i.inFlightPromptIds.add(xe);try{this.stashE1RaiseContent(F,J,Y);try{await this.appSyncClient.createEvent({sessionId:e,type:p.EventType.INTERACTIVE_PROMPT,source:p.EventSource.DESKTOP,content:C,metadata:Z,...(0,p.raiseFieldsFromRecord)(H),timestamp:(0,p.prepareEventTimestamp)({orderingKey:e}),isEncrypted:yt?!0:void 0}),s.info("Q[next] emit succeeded",{sessionId:e,promptId:F,remaining:i.pendingQuestionsQueue?.length??0})}catch(G){let X=this.activeSessions.get(e),ee=X?.promptGenerationToken;ee&&ee.gen===ie.gen&&ee.promptId===ie.promptId?(this.promoteFingerprintAndClearPromptState(X),s.warn("Q[next] emit failed; promoted fingerprint + cleared prompt state. User must answer remaining questions on desktop terminal.",{sessionId:e,promptId:F,error:G instanceof Error?G.message:String(G)})):s.warn("Q[next] emit failed but a NEW prompt replaced our token during await; not clearing state (would wipe new prompt). Q[next..QN] of the original AskUserQuestion are lost; new prompt continues normally.",{sessionId:e,tokenAtAwait:ie,currentToken:ee,error:G instanceof Error?G.message:String(G)})}}finally{i.inFlightPromptIds.delete(xe)}}else try{await this.sendPromptError(e,"Failed to select option")}catch(T){s.warn("[walker] sendPromptError threw \u2014 relying on idempotent mark in finally",{sessionId:e,eventId:t.eventId,error:String(T)})}finally{await this.markEventExecutedIdempotent(i,t)}}else if(v.action==="option_with_followup"){if(i.pendingQuestionsQueue!==void 0){let A=this.getWalkerPosition(i),O=i.pendingCurrentQuestion?.options?.length??0,_=v.option?parseInt(v.option,10):NaN,Q=Number.isFinite(_)&&_>O,T=v.followUpText??"";if(s.info("AUQ walker \u2014 option_with_followup dispatch",{sessionId:e,option:v.option,optionNum:_,explicitCount:O,isSynthDigit:Q,walkerPosition:A,followUpTextLen:T.length}),await this.markEventExecutedIdempotent(i,t),E())return;if(Q){if(A==="on_synth"){let x=await this.answerVerifiedInteractivePrompt(i,I,"2",{pressEnter:!1});if(E())return;if(!x){try{await this.emitUserChoice(e,"AskUserQuestion cancel keypress failed (tmux unavailable); your reply was not sent")}catch(C){s.warn("emitUserChoice on synth-digit on-SYNTH Cancel failed",{sessionId:e,error:C instanceof Error?C.message:String(C)})}return}if(await new Promise(C=>setTimeout(C,1500)),E())return;if(T){let C=await this.sendFreeFormWhenNoChooser(e,i,T,S);if(E())return;C||s.warn("on-SYNTH Cancel followup text-send failed",{sessionId:e})}try{await this.emitUserChoice(e,"\u2192 Cancel \u2014 AskUserQuestion cancelled, sending your reply as a new prompt")}catch(C){s.warn("emitUserChoice on synth-digit on-SYNTH success failed",{sessionId:e,error:C instanceof Error?C.message:String(C)})}if(E())return;this.promoteFingerprintAndClearPromptState(i);return}await this.handleMobileReplyAsDismissAndPrompt(e,i,I,T,S,E);return}let k=y?.[v.option]||v.option,H=_-1,F=i.pendingCurrentQuestion?.options?.[H],q=typeof F=="string"?F:F&&typeof F=="object"?F.label:`option ${v.option}`;if(A==="q1"){let x=await this.answerVerifiedInteractivePrompt(i,I,k,{pressEnter:!1});if(E())return;if(!x){try{await this.sendPromptError(e,"Failed to select option. Your reply (including the follow-up text) was not sent. Please retry.")}catch(C){s.warn("sendPromptError on q1 option_with_followup failed",{sessionId:e,error:C instanceof Error?C.message:String(C)})}return}if(await new Promise(C=>setTimeout(C,1500)),E())return;if(T){let C=await this.sendFreeFormWhenNoChooser(e,i,T,S);if(E())return;C||s.warn("q1 option_with_followup followup text-send failed",{sessionId:e})}try{await this.emitUserChoice(e,T?`\u2192 ${q} + sending your reply as a new prompt`:`\u2192 ${q}`)}catch(C){s.warn("emitUserChoice on q1 option_with_followup success failed",{sessionId:e,error:C instanceof Error?C.message:String(C)})}if(E())return;this.promoteFingerprintAndClearPromptState(i);return}if(A==="nq_mid"){let x=await this.answerVerifiedInteractivePrompt(i,I,k,{pressEnter:!1});if(E())return;if(!x){try{await this.sendPromptError(e,"Failed to commit Q[i]. Your reply was not sent. Please retry.")}catch(Z){s.warn("sendPromptError on nq_mid option_with_followup failed",{sessionId:e,error:Z instanceof Error?Z.message:String(Z)})}return}if(await new Promise(Z=>setTimeout(Z,200)),E())return;let C=i.pendingQuestionsQueue?.shift();C?(i.pendingCurrentQuestion=C,i.pendingPanePromptIdentity=this.buildAskUserQuestionPaneIdentity(C,i.activeAskUserQuestionInvocation?.occurrenceId)):s.warn("nq_mid option_with_followup: queue.shift returned undefined unexpectedly",{sessionId:e}),await this.handleMobileReplyAsDismissAndPrompt(e,i,i.pendingPanePromptIdentity,T,S,E);return}if(A==="nq_last"){await this.handleOptionWithFollowupOnLastQ(e,i,I,k,q,T,S,E);return}let z=await this.answerVerifiedInteractivePrompt(i,I,k,{pressEnter:!1});if(E())return;if(!z){try{await this.sendPromptError(e,"Failed to send SYNTH keypress. Your reply was not sent. Please retry.")}catch(x){s.warn("sendPromptError on on-SYNTH option_with_followup failed",{sessionId:e,error:x instanceof Error?x.message:String(x)})}return}if(await new Promise(x=>setTimeout(x,1500)),E())return;if(T){let x=await this.sendFreeFormWhenNoChooser(e,i,T,S);if(E())return;x||s.warn("on-SYNTH option_with_followup text-send failed",{sessionId:e})}let J=i.pendingCurrentQuestion?._isSubmit===!0,Y=q.toLowerCase(),W;J&&Y==="cancel"?W=T?"\u2192 Cancel \u2014 AskUserQuestion cancelled, sending your reply as a new prompt":"\u2192 Cancel \u2014 AskUserQuestion cancelled, no answers submitted":J&&Y.startsWith("submit")?W=T?"\u2192 Submit answers \u2014 AskUserQuestion completed, sending your reply as a new prompt":"\u2192 Submit answers \u2014 AskUserQuestion completed":W=T?`\u2192 ${q} + sending your reply as a new prompt`:`\u2192 ${q}`;try{await this.emitUserChoice(e,W)}catch(x){s.warn("emitUserChoice on on-SYNTH option_with_followup success failed",{sessionId:e,error:x instanceof Error?x.message:String(x)})}if(E())return;this.promoteFingerprintAndClearPromptState(i);return}let M=y?.[v.option]||v.option;s.info("User selected option with follow-up",{option:v.option,terminalInput:M,followUpText:v.followUpText});let b=await this.answerVerifiedInteractivePrompt(i,I,M);if(E()){await this.markEventExecutedIdempotent(i,t);return}if(b){if(await this.markEventExecutedIdempotent(i,t),E())return;if(!g){s.warn("emitAnswerAck called without promptId \u2014 clearing state + skipping ack",{sessionId:e,source:"option_with_followup",eventId:t.eventId}),this.promoteFingerprintAndClearPromptState(i);return}try{await this.emitAnswerAck(e,`Selected option ${v.option}`,{promptId:g,questionIndex:0,isTerminal:!0})}catch(A){s.warn("[walker] emitAnswerAck (option_with_followup) failed \u2014 continuing to clearPromptState + executeMobilePrompt",{sessionId:e,promptId:g,error:A instanceof Error?A.message:String(A)})}if(E())return;if(this.promoteFingerprintAndClearPromptState(i),v.followUpText){await new Promise(O=>setTimeout(O,1e3));let A={...t,content:v.followUpText};await this.executeMobilePrompt(e,A)}}else try{await this.sendPromptError(e,"Failed to select option. Your reply (including the follow-up text) was not sent. Please retry.")}catch(A){s.warn("[walker] sendPromptError threw \u2014 relying on idempotent mark in finally",{sessionId:e,eventId:t.eventId,error:String(A)})}finally{await this.markEventExecutedIdempotent(i,t)}}else{if(i.pendingQuestionsQueue!==void 0){let b=this.getWalkerPosition(i);if(s.info("AUQ walker \u2014 send_as_response dispatch",{sessionId:e,walkerPosition:b,contentPreview:f.slice(0,80)}),await this.markEventExecutedIdempotent(i,t),E())return;if(b==="on_synth"){let A=await this.answerVerifiedInteractivePrompt(i,I,"2",{pressEnter:!1});if(E())return;if(!A){try{await this.emitUserChoice(e,"AskUserQuestion cancel keypress failed (tmux unavailable); your reply was not sent")}catch(_){s.warn("emitUserChoice on send_as_response on-SYNTH Cancel failed",{sessionId:e,error:_ instanceof Error?_.message:String(_)})}return}if(await new Promise(_=>setTimeout(_,1500)),E())return;let O=await this.sendFreeFormWhenNoChooser(e,i,f,S);if(E())return;O||s.warn("send_as_response on-SYNTH text-send failed",{sessionId:e});try{await this.emitUserChoice(e,"\u2192 Cancel \u2014 AskUserQuestion cancelled, sending your reply as a new prompt")}catch(_){s.warn("emitUserChoice on send_as_response on-SYNTH success failed",{sessionId:e,error:_ instanceof Error?_.message:String(_)})}if(E())return;this.promoteFingerprintAndClearPromptState(i);return}await this.handleMobileReplyAsDismissAndPrompt(e,i,I,f,S,E);return}s.info("Rejecting legacy prompt before forwarding free-form mobile input",{responsePreview:f.slice(0,80),attachmentCount:t.attachments?.length??0});let M=await this.dismissLivePromptForConversation(i,d);if(E()){await this.markEventExecutedIdempotent(i,t);return}if(M)await this.clearInteractivePromptsForAcceptedInput(i),await new Promise(b=>setTimeout(b,200)),await this.executeMobilePrompt(e,{...t,content:f});else try{await this.sendPromptError(e,"Failed to dismiss the active prompt; your message was not sent")}catch(b){s.warn("[walker] sendPromptError threw \u2014 relying on idempotent mark in finally",{sessionId:e,eventId:t.eventId,error:String(b)})}finally{await this.markEventExecutedIdempotent(i,t)}}}finally{P&&i.inFlightPromptIds&&i.inFlightPromptIds.delete(P),w&&i.inFlightEventIds&&i.inFlightEventIds.delete(w)}}async sendInteractivePromptAsync(e,t,n){let i=this.activeSessions.get(e),r=i?.activeAskUserQuestionInvocation?.occurrenceId,o=i?.promptGenerationToken?{...i.promptGenerationToken}:void 0,c=(0,p.prepareEventTimestamp)({orderingKey:e});await new Promise(b=>setTimeout(b,500));let a=process.env.CODEVIBE_TMUX_SESSION,l={...t.metadata||{}},d,P=t.metadata?.tool_name,w=t.metadata?.tool_input,f=P==="AskUserQuestion"&&Array.isArray(w?.questions)?w.questions:[];if(f.length>0&&Array.isArray(f[0]?.options)&&f[0].options.length>0){let b=f[0];d=this.buildAskUserQuestionPaneIdentity(b,r);let A=[];if(a)try{let{exec:k}=await import("child_process"),H=q=>new Promise((z,J)=>{k(q,{timeout:5e3},(Y,W)=>{Y?J(Y):z({stdout:W||""})})}),{stdout:F}=await H(`tmux capture-pane -p -e -S -30 -t '${a}'`);A=this.parseAskUserQuestionSynthTail(F),s.info("AskUserQuestion synth-tail parsed from tmux",{tailCount:A.length,tail:A.map(q=>q.label)})}catch(k){s.warn("Failed to capture tmux for AskUserQuestion synth-tail; emitting without synth tail",{error:k instanceof Error?k.message:String(k)})}else s.info("No tmux session \u2014 AskUserQuestion synth tail will be empty");let O=this.activeSessions.get(e);if(O){let k=O.promptGenerationToken;o&&k?.gen===o.gen?O.pendingSynthTail=A:s.warn("AskUserQuestion synth-tail: stale async \u2014 token gen mismatch, skipping pendingSynthTail write",{tokenAtEmit:o,currentToken:k,sessionId:e})}let _=this.buildQuestionWireData(b,A);l.options=JSON.parse(JSON.stringify(_.options)),l.submitMap=JSON.parse(JSON.stringify(_.submitMap)),l.instructions=_.instructions,l.tool_name="AskUserQuestion",l.tool_input={questions:[b]},n=b.question;let Q=typeof t.prompt_id=="string"&&t.prompt_id.length>0,T=Yt(f);if(Q){let k=this.activeSessions.get(e);if(k){let H=k.promptGenerationToken;o&&H?.gen===o.gen?(k.pendingQuestionsQueue=T.remainingQueue,k.pendingCurrentQuestion=b,k.pendingPanePromptIdentity=d,k.hasReviewScreen=T.hasReviewScreen):s.warn("AskUserQuestion: stale async \u2014 token gen mismatch, skipping walker-state write",{tokenAtEmit:o,currentToken:H,sessionId:e})}}else s.warn("AskUserQuestion: empty prompt_id, degrading to single-Q legacy emit",{questionCount:f.length});s.info("AskUserQuestion: emitting Q1 only (Q2..QN queued)",{questionCount:T.questionCount,hasReviewScreen:T.hasReviewScreen,queuedRemaining:Q?T.remainingQueue.length:0,optionCountFirst:_.options.length,questionPreview:b.question.slice(0,80)})}else if(a)try{let{exec:b}=await import("child_process"),A=T=>new Promise((k,H)=>{b(T,{timeout:5e3},(F,q)=>{F?H(F):k({stdout:q||""})})}),{stdout:O}=await A(`tmux capture-pane -p -e -S -30 -t '${a}'`),_=O.split(`
|
|
7
|
-
`);s.info("tmux capture result",{tmuxSession:a,totalLines:_.length,lastLines:_.slice(-15).map(T=>T.replace(/\x1B[^m]*m/g,"").trim()).filter(Boolean)});let Q=(0,p.parseInteractivePrompt)(O);if(Q&&Q.options.length>0)l.options=Q.options,l.submitMap=Q.submitMap,l.instructions=this.buildPromptInstructions(Q),d=this.buildParsedPanePromptIdentity(Q),s.info("Parsed dynamic options from tmux",{optionCount:Q.options.length,kind:Q.kind,options:Q.options});else{this.suppressPromptOptionsUnavailable(e,o,"tmux parse returned no options");return}}catch(
|
|
8
|
-
`).slice(-14),i=[],r=new Set,o=/^\s*(?:[›❯▸▶➜➤]\s*)?(?:\d+\.\s+)?(Type something|Chat about this)\.?\s*$/i;for(let c of n){let a=c.match(o);if(!a)continue;let d=a[1].toLowerCase()==="type something"?"Type something":"Chat about this";r.has(d)||(i.push({label:d}),r.add(d))}return i}suppressPromptOptionsUnavailable(e,t,n){s.warn("Interactive prompt: real options unavailable \u2014 suppressing mobile prompt (no fabricated options)",{sessionId:e,reason:n});let i=t?.promptId,r=i?this.e1PromptRaises.get(i):void 0;r&&(r.record.mobileActionable=!1,this.emitSuppressedPromptCarrier(e,r.record).catch(c=>{s.error("E1: failed to emit suppressed-prompt badge carrier",{sessionId:e,promptId:r.record.promptId,error:c instanceof Error?c.message:String(c)})}));let o=this.activeSessions.get(e);o&&t&&o.promptGenerationToken?.gen===t.gen&&this.clearPromptState(o)}async emitSuppressedPromptCarrier(e,t){let n="\u26A0\uFE0F A prompt is waiting in your desktop terminal, but its options could not be shown here. Please answer it on your desktop.",
|
|
5
|
+
${ct(u,"stdout")}`;return/^can't find session:/m.test(e)||/^no server running on /m.test(e)||/^error connecting to .* \((?:No such file or directory|Connection refused)\)$/m.test(e)?"absent":"unknown"}var Vt=1800*1e3,Ht=60*1e3,Wt=1440*60*1e3;async function jt(u,e){let t=async(n,i)=>{try{await we(n)}catch(r){s.warn("tmux send-keys failed during self-terminate",{sessionName:u,label:i,error:String(r)})}};await t(`tmux send-keys -t "${u}" C-c`,"ctrl-c"),await new Promise(n=>setTimeout(n,200)),await t(`tmux send-keys -t "${u}" -l "${e}"`,"quit-text"),await new Promise(n=>setTimeout(n,500)),await t(`tmux send-keys -t "${u}" Enter`,"enter")}var zt={question:"Ready to submit your answers?",options:[{label:"Submit answers",description:"Send your selections to the assistant"},{label:"Cancel",description:"Discard your answers"}],multiSelect:!1,_isSubmit:!0};function Yt(u){let e=u.length,t=e>=2,n=u.slice(1);return t&&n.push({...zt}),{questionCount:e,hasReviewScreen:t,remainingQueue:n}}var Pe=class u{constructor(e){this.activeSessions=new Map;this.assignedPort=0;this.portRefreshTimer=null;this.tmuxLifecycleTimer=null;this.tmuxLifecycleCheckInFlight=!1;this.tmuxLifecycleObservedAlive=!1;this.tmuxLifecycleInitDeadlineMs=Number.POSITIVE_INFINITY;this.stopPromise=null;this.e1TtlRefreshTimer=null;this.encryptedKeyRecoveryTimer=null;this.encryptedKeyRecoveryAttempt=0;this.encryptedKeyRecovery=null;this.sessionKey=null;this.sessionKeyGen=null;this.sessionIsEncrypted=!1;this.encryptionResolved=!0;this.toolActivity=null;this.toolActivityLegacyRecorders=new Map;this.claudeToBackendSessionId=new Map;this.sessionBindingStore=new de(s);this.sessionStartChain=Promise.resolve();this.sessionLifecycleQueueDepth=0;this.retiredSessionRecoveryPromises=new Map;this.lifecycleGeneration=0;this.isStopping=!1;this.currentClaudeSessionId=null;this.claudeOwnershipGeneration=0;this.claudeSessionIntentVersions=new Map;this.endedOrSupersededClaudeSessionIds=new Set;this.nestedClaudeSessionIds=new Set;this.pendingMobilePrompts=new Map;this.mobilePromptQueueStore=new le(s);this.queuedMobilePrompts=new Map;this.queuedMobilePromptDrains=new Set;this.queuedMobilePromptTimers=new Map;this.queuedMobilePromptAdmissions=new Map;this.e1PromptRaises=new Map;this.promptPublications=new p.PromptPublicationTracker;this.e1NeedsReRaise=new Set;this.e1NeedsResolution=new Set;this.e1ResolutionInFlight=new Map;this.e1ParkedOrphans=new Map;this.nextPromptGen=1;this.httpApi=new he(()=>this.getDaemonHealth()),this.commandExecutor=new ge,this.promptResponder=new fe,this.initialSessionId=e,this.currentClaudeSessionId=e??null}static{this.E1_TTL_REFRESH_MS=300*1e3}static{this.MOBILE_PROMPT_EXPIRY_MS=3e3}static{this.MOBILE_PROMPT_QUEUE_MAX_ATTEMPTS=8}static{this.MOBILE_PROMPT_QUEUE_MAX_BACKOFF_MS=3e4}newE1Raise(e,t,n){let{record:i}=(0,p.newPromptRaise)({sessionId:e,producerKind:"PLUGIN_APPROVAL",mobileActionable:t,agentType:"CLAUDE",...n!==void 0&&{title:n}});return this.e1PromptRaises.set(i.promptId,{record:i}),i}stashE1RaiseContent(e,t,n){let i=this.e1PromptRaises.get(e);i&&(i.contentPlain=t,i.metadataPlain=n)}snapshotOpenE1(e){return[...this.e1PromptRaises.values()].filter(t=>t.record.sessionId===e&&!this.e1NeedsResolution.has(t.record.promptId)).map(t=>({record:{...t.record},...t.contentPlain!==void 0&&{contentPlain:t.contentPlain},...t.metadataPlain!==void 0&&{metadataPlain:t.metadataPlain}}))}async snapshotSettledOpenE1(e){for(;;){let t=[...this.e1PromptRaises.values()].filter(n=>n.record.sessionId===e&&this.promptPublications.isPublishing(n.record.promptId));if(t.length===0)return this.snapshotOpenE1(e);await Promise.all(t.map(n=>this.promptPublications.waitFor(n.record.promptId)))}}async reRaiseSnapshotE1(e,t,n){if(e.length===0)return[];s.info("E1: re-raising open prompts onto replacement session",{replacementSessionId:t,count:e.length});let i=[];for(let r of e){let o={record:{...r.record,sessionId:t},...r.contentPlain!==void 0&&{contentPlain:r.contentPlain},...r.metadataPlain!==void 0&&{metadataPlain:r.metadataPlain}};this.e1PromptRaises.set(o.record.promptId,o),!await this.reRaiseOneE1(t,o,n?{untilAck:n}:void 0)&&!o.resolutionStarted&&i.push(r)}return i}parkE1Orphans(e,t){if(t.length===0)return;let n=new Map;for(let i of this.e1ParkedOrphans.get(e)??[])n.set(i.record.promptId,i);for(let i of t)n.set(i.record.promptId,i);this.e1ParkedOrphans.set(e,[...n.values()]),s.info("E1: parked retired-session open prompts for replacement re-home",{claudeSessionId:e,count:n.size})}async drainParkedE1Orphans(e,t,n){let i=this.e1ParkedOrphans.get(e);if(!i||i.length===0)return;let r=await this.reRaiseSnapshotE1(i,t,n);if(r.length===0){this.e1ParkedOrphans.delete(e);return}this.e1ParkedOrphans.set(e,r),s.info("E1: retirement drain aborted before ACK \u2014 retained durable park for next bootstrap",{claudeSessionId:e,liveSessionId:t,retained:r.length})}async reRaiseOneE1(e,t,n){let i=t.record;if(this.e1NeedsResolution.has(i.promptId)||!(0,p.canRecoverPromptRaise)(t,this.promptPublications.isPublishing(i.promptId)))return!1;let r=i.mobileActionable&&t.contentPlain!==void 0,o=n?.untilAck,c=4;for(let a=1;;a++){if(!(0,p.isOpenPromptRaise)(t,this.e1PromptRaises.get(i.promptId),this.e1NeedsResolution.has(i.promptId)))return!1;if(o&&!o())return this.e1NeedsReRaise.add(i.promptId),s.info("E1: re-raise wait aborted (lifecycle superseded/stopping) \u2014 flagged for TTL-drain",{sessionId:e,promptId:i.promptId}),!1;try{if(r){let l=this.encryptForEmit(t.contentPlain,t.metadataPlain??{},p.EventType.INTERACTIVE_PROMPT,e);if(!l)return!1;await this.promptPublications.run(i.promptId,()=>this.appSyncClient.createEvent({sessionId:e,type:p.EventType.INTERACTIVE_PROMPT,source:p.EventSource.DESKTOP,content:l.content,metadata:l.metadata,...(0,p.raiseFieldsFromRecord)(i),timestamp:(0,p.prepareEventTimestamp)({orderingKey:e}),isEncrypted:l.isEncrypted?!0:void 0}))}else if(i.mobileActionable=!1,!await this.emitSuppressedPromptCarrier(e,i))return!1;return this.e1NeedsReRaise.delete(i.promptId),s.info("E1: re-raised prompt onto replacement session",{sessionId:e,promptId:i.promptId,actionable:r}),!0}catch(l){if(!o&&a>=c)return this.e1NeedsReRaise.add(i.promptId),s.error("E1: re-raise failed after retries \u2014 flagged for TTL-drain re-attempt",{sessionId:e,promptId:i.promptId,error:l instanceof Error?l.message:String(l)}),!1;if(o){let d=Date.now()+Math.min(250*2**(a-1),4e3);for(;Date.now()<d&&o();)await new Promise(P=>setTimeout(P,Math.min(50,d-Date.now())))}else await new Promise(d=>setTimeout(d,250*a))}}}async drainPendingE1ReRaises(){for(let e of[...this.e1NeedsReRaise]){if(this.e1NeedsResolution.has(e))continue;let t=this.e1PromptRaises.get(e);if(!t){this.e1NeedsReRaise.delete(e);continue}this.activeSessions.has(t.record.sessionId)&&await this.reRaiseOneE1(t.record.sessionId,t)}}async resolveE1PluginApproval(e,t){let n=this.e1ResolutionInFlight.get(t);if(n)return n;let i=this.e1PromptRaises.get(t);if(!i)return this.e1NeedsResolution.delete(t),this.e1NeedsReRaise.delete(t),!0;i.resolutionStarted=!0,this.e1NeedsResolution.add(t);let r=(async()=>{try{this.promptPublications.isPublishing(t)&&await this.promptPublications.waitFor(t);let o=await this.appSyncClient.createEvent({sessionId:i.record.sessionId||e,type:p.EventType.NOTIFICATION,source:p.EventSource.DESKTOP,resolvedPromptId:t,ownerToken:i.record.ownerToken});return o?.promptId!==t?(s.warn("E1: plugin prompt resolution returned without exact prompt acknowledgement",{sessionId:e,promptId:t,acknowledgedPromptId:o?.promptId}),!1):(this.e1NeedsResolution.delete(t),this.e1NeedsReRaise.delete(t),this.e1PromptRaises.delete(t),s.info("E1: plugin prompt resolution acknowledged",{sessionId:e,promptId:t}),!0)}catch(o){return s.warn("E1: plugin prompt resolution failed; retained for retry",{sessionId:e,promptId:t,error:o instanceof Error?o.message:String(o)}),!1}})();this.e1ResolutionInFlight.set(t,r);try{return await r}finally{this.e1ResolutionInFlight.get(t)===r&&this.e1ResolutionInFlight.delete(t)}}async clearInteractivePromptsForAcceptedInput(e){let t=[...this.e1PromptRaises.values()].filter(r=>r.record.sessionId===e.sessionId).map(r=>r.record.promptId);if(!!!(e.waitingForPromptResponse||e.activeAskUserQuestionInvocation||e.pendingAskUserQuestionInvocations?.length)&&t.length===0)return;let i=new Set(e.pendingAskUserQuestionInvocations?.map(r=>r.fingerprint)??[]);if(e.activeAskUserQuestionInvocation?.fingerprint&&i.add(e.activeAskUserQuestionInvocation.fingerprint),i.size>0){e.completedAskUserQuestionFingerprints||(e.completedAskUserQuestionFingerprints=new Set);for(let r of i)e.completedAskUserQuestionFingerprints.add(r)}this.clearPromptState(e),e.activeAskUserQuestionInvocation=void 0,e.pendingAskUserQuestionInvocations=[];for(let r of t)await this.resolveE1PluginApproval(e.sessionId,r)}async drainPendingE1Resolutions(){for(let e of[...this.e1NeedsResolution]){let t=this.e1PromptRaises.get(e);if(!t){this.e1NeedsResolution.delete(e);continue}await this.resolveE1PluginApproval(t.record.sessionId,e)}}getDaemonHealth(){let e=this.encryptedKeyRecovery;return e?{status:"degraded",reason:"encrypted_session_key_unavailable",sessionId:e.backendSessionId,retryAttempt:this.encryptedKeyRecoveryAttempt}:{status:"healthy"}}clearEncryptedKeyRecovery(e,t,n){t&&this.encryptedKeyRecovery?.claudeSessionId!==t||n&&this.encryptedKeyRecovery?.backendSessionId!==n||(this.encryptedKeyRecoveryTimer&&(clearTimeout(this.encryptedKeyRecoveryTimer),this.encryptedKeyRecoveryTimer=null),this.encryptedKeyRecovery&&s.info("Encrypted-session key recovery cleared",{claudeSessionId:this.encryptedKeyRecovery.claudeSessionId,backendSessionId:this.encryptedKeyRecovery.backendSessionId,reason:e}),this.encryptedKeyRecovery=null,this.encryptedKeyRecoveryAttempt=0)}enterEncryptedKeyRecovery(e,t){let n=e.session_id,i=this.encryptedKeyRecovery;return!i||i.claudeSessionId!==n||i.backendSessionId!==t?(this.clearEncryptedKeyRecovery("replaced by a different recovery target"),this.encryptedKeyRecovery={claudeSessionId:n,backendSessionId:t,payload:e,livenessInvalidated:!1}):(i.payload=e,i.livenessInvalidated=!1,this.encryptedKeyRecoveryTimer&&(clearTimeout(this.encryptedKeyRecoveryTimer),this.encryptedKeyRecoveryTimer=null)),this.encryptedKeyRecovery}async invalidateEncryptedRecoveryLiveness(e){for(;!this.isStopping&&this.encryptedKeyRecovery===e;){let t=await this.appSyncClient.invalidateHeartbeat(e.backendSessionId);if(this.isStopping||this.encryptedKeyRecovery!==e)return!1;if(t)return e.livenessInvalidated=!0,this.scheduleEncryptedKeyRecovery(e.payload,e.backendSessionId),!0;s.warn("Retrying durable invalidation before encrypted-key recovery",{claudeSessionId:e.claudeSessionId,backendSessionId:e.backendSessionId}),await new Promise(n=>setTimeout(n,1e3))}return!1}scheduleEncryptedKeyRecovery(e,t){if(this.isStopping)return;let n=e.session_id,i=this.encryptedKeyRecovery;if(!i||i.claudeSessionId!==n||i.backendSessionId!==t){let c=this.enterEncryptedKeyRecovery(e,t);this.invalidateEncryptedRecoveryLiveness(c);return}else i.payload=e;if(!i.livenessInvalidated||this.encryptedKeyRecoveryTimer)return;let r=Math.min(6e4,5e3*2**Math.min(this.encryptedKeyRecoveryAttempt,4));this.encryptedKeyRecoveryAttempt+=1;let o=this.encryptedKeyRecovery;o&&(s.warn("Encrypted session is keyless; scheduling automatic resume recovery",{claudeSessionId:n,backendSessionId:t,attempt:this.encryptedKeyRecoveryAttempt,delayMs:r}),this.encryptedKeyRecoveryTimer=setTimeout(()=>{this.encryptedKeyRecoveryTimer=null,!(this.isStopping||this.encryptedKeyRecovery!==o)&&this.handleSessionStart(o.payload,o.backendSessionId,"self-heal").catch(c=>{s.warn("Encrypted-session key recovery attempt failed",{claudeSessionId:n,backendSessionId:t,error:String(c)})}).finally(()=>{!this.isStopping&&this.encryptedKeyRecovery===o&&!this.encryptedKeyRecoveryTimer&&this.scheduleEncryptedKeyRecovery(o.payload,o.backendSessionId)})},r),this.encryptedKeyRecoveryTimer.unref?.())}getPort(){return this.assignedPort}generateBackendSessionId(e){return`claude-${e}`}trackMobilePrompt(e,t){this.pendingMobilePrompts.has(e)||this.pendingMobilePrompts.set(e,[]),this.pendingMobilePrompts.get(e).push({prompt:t.trim(),timestamp:Date.now()}),s.debug("Tracking mobile prompt for deduplication",{sessionId:e,promptLength:t.length})}isRecentMobilePrompt(e,t){let n=this.pendingMobilePrompts.get(e);if(!n)return!1;let i=Date.now(),r=t.trim(),o=[],c=!1;for(let a of n)if(!(i-a.timestamp>u.MOBILE_PROMPT_EXPIRY_MS)){if(!c&&a.prompt===r){c=!0,s.debug("Found matching mobile prompt, filtering duplicate",{sessionId:e});continue}o.push(a)}return o.length>0?this.pendingMobilePrompts.set(e,o):this.pendingMobilePrompts.delete(e),c}writePortFile(e){let t=L.join(V.tmpdir(),`codevibe-claude-${e}.port`);try{$.writeFileSync(t,this.assignedPort.toString()),s.info(`Port file written: ${t} -> ${this.assignedPort}`)}catch(n){s.error(`Failed to write port file: ${t}`,n)}}removePortFile(e){let t=L.join(V.tmpdir(),`codevibe-claude-${e}.port`);try{$.existsSync(t)&&($.unlinkSync(t),s.info(`Port file removed: ${t}`))}catch(n){s.warn(`Failed to remove port file: ${t}`,n)}}hasOtherLiveDaemonForSession(e){try{let t=(0,Ee.execSync)("ps -eww -o pid= -o args=",{encoding:"utf8",timeout:2e3}),n=process.pid;for(let i of t.split(`
|
|
6
|
+
`)){let r=i.trim();if(!r)continue;let o=r.indexOf(" ");if(o<0)continue;let c=parseInt(r.substring(0,o),10);if(isNaN(c)||c===n)continue;let a=r.substring(o+1);if(/node.*codevibe-claude.*server\.js/.test(a)&&a.includes(e))return!0}return!1}catch(t){return s.warn('hasOtherLiveDaemonForSession: ps query failed; falling back to "no other daemon"',{error:String(t)}),!1}}async start(){try{if(s.info("Starting CodeVibe MCP Server...",{environment:(0,p.getEnvironment)()}),this.appSyncClient=new p.AppSyncClient,await this.appSyncClient.authenticateWithStoredTokens()){s.info("Authenticated with stored OAuth tokens",{userId:this.appSyncClient.getCurrentUserId(),email:this.appSyncClient.getCurrentUserEmail()}),await(0,p.registerDeviceEncryptionKey)(this.appSyncClient,s),(0,p.startDeviceKeyWatcher)(this.appSyncClient,s),(0,p.pushDetectedAgents)(this.appSyncClient,s).catch(t=>{s.warn("Failed to update available agents (non-fatal)",{error:t?.message})});try{let t=await this.appSyncClient.sweepOrphanSessions({agentType:"CLAUDE"});t>0&&s.info("Orphan sweep: marked stale Claude sessions INACTIVE",{swept:t})}catch(t){s.warn("Orphan sweep failed, continuing startup",{error:t instanceof Error?t.message:String(t)})}}else s.error('Authentication failed. Run "codevibe login" first.'),console.error('Not authenticated. Run "codevibe login" to sign in.'),process.exit(1);this.httpApi.onEvent(this.handleEventFromHook.bind(this)),this.assignedPort=await this.httpApi.start(this.initialSessionId),this.startPortKeepalive(),this.startTmuxLifecycleMonitor(),s.info("MCP Server started successfully",{port:this.assignedPort,host:(0,p.getConfig)().server.host,dynamicPort:(0,p.getConfig)().server.dynamicPort,sessionId:this.initialSessionId,authenticated:this.appSyncClient.isAuthenticated(),userId:this.appSyncClient.getCurrentUserId()})}catch(e){throw s.error("Failed to start MCP Server:",e),e}}startPortKeepalive(){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(Wt,Math.max(Ht,e)):Vt;this.portRefreshTimer=setInterval(()=>this.refreshPortFiles(),t),this.e1TtlRefreshTimer&&clearInterval(this.e1TtlRefreshTimer),this.e1TtlRefreshTimer=setInterval(()=>{this.refreshE1Ttls()},u.E1_TTL_REFRESH_MS)}async refreshE1Ttls(){await this.drainPendingE1Resolutions(),await this.drainPendingE1ReRaises();let e=[...this.e1PromptRaises.values()].filter(t=>this.activeSessions.has(t.record.sessionId)&&!this.e1NeedsResolution.has(t.record.promptId)&&(0,p.canRecoverPromptRaise)(t,this.promptPublications.isPublishing(t.record.promptId))).map(t=>t.record.promptId);if(e.length!==0)try{let t=await this.appSyncClient.refreshOpenPromptTtl(e);for(let n of t){let i=this.e1PromptRaises.get(n);i&&this.activeSessions.has(i.record.sessionId)&&await this.reRaiseOneE1(i.record.sessionId,i)}}catch(t){s.warn("E1: TTL-refresh tick failed (non-fatal, retries next tick)",{error:t instanceof Error?t.message:String(t)})}}refreshPortFiles(){let e=new Date;for(let n of this.activeSessions.values()){let i=L.join(V.tmpdir(),`codevibe-claude-${n.claudeSessionId}.port`);try{$.utimesSync(i,e,e)}catch(o){o?.code==="ENOENT"?this.writePortFile(n.claudeSessionId):s.warn(`Port-file refresh failed: ${i}`,o)}let r=L.join(V.tmpdir(),`codevibe-claude-${n.claudeSessionId}.pid`);try{$.utimesSync(r,e,e)}catch(o){o?.code!=="ENOENT"&&s.warn(`Pid-file refresh failed: ${r}`,o)}}let t=process.env[j];if(t){let n=L.join(V.tmpdir(),`codevibe-claude-instance-${t}.json`);try{$.utimesSync(n,e,e)}catch(i){i?.code!=="ENOENT"&&s.warn(`Instance-map refresh failed: ${n}`,i)}}}startTmuxLifecycleMonitor(){if(this.tmuxLifecycleTimer)return;let e=process.env[j];e&&(this.tmuxLifecycleInitDeadlineMs=Date.now()+Bt,this.tmuxLifecycleTimer=setInterval(()=>{this.checkTmuxLifecycle(e)},qt),this.tmuxLifecycleTimer.unref?.(),this.checkTmuxLifecycle(e))}async tmuxSessionState(e){try{return await Lt("tmux",["has-session","-t",e]),"alive"}catch(t){let n=pt(t);return n==="absent"?n:(s.warn("Could not inspect native Claude tmux session; preserving daemon",{tmuxSession:e,error:t instanceof Error?t.message:String(t)}),"unknown")}}async checkTmuxLifecycle(e){if(!(this.isStopping||this.tmuxLifecycleCheckInFlight)){this.tmuxLifecycleCheckInFlight=!0;try{let t=await this.tmuxSessionState(e);if(t==="alive"){this.tmuxLifecycleObservedAlive=!0;return}let n=this.tmuxLifecycleObservedAlive||Date.now()>=this.tmuxLifecycleInitDeadlineMs;if(t==="unknown"||!n||this.isStopping)return;this.stopTmuxLifecycleMonitor(),s.info("Native Claude tmux session ended; stopping companion daemon",{tmuxSession:e}),this.stop().then(()=>process.exit(0),i=>{s.error("Failed to stop companion daemon after native session end",{error:i instanceof Error?i.message:String(i)}),process.exit(1)})}finally{this.tmuxLifecycleCheckInFlight=!1}}}stopTmuxLifecycleMonitor(){this.tmuxLifecycleTimer&&(clearInterval(this.tmuxLifecycleTimer),this.tmuxLifecycleTimer=null)}async shouldShutdownForSignal(e){if(e!=="SIGHUP")return!0;let t=process.env[j];if(!t)return!0;let n=await this.tmuxSessionState(t);return n==="absent"?!0:(s.info("Ignoring SIGHUP while native Claude tmux session remains available",{tmuxSession:t,state:n}),!1)}stop(){return this.stopPromise?this.stopPromise:(this.stopPromise=this.doStop(),this.stopPromise)}async doStop(){s.info("Stopping MCP Server..."),this.isStopping=!0,this.stopTmuxLifecycleMonitor(),this.lifecycleGeneration+=1,this.clearEncryptedKeyRecovery("daemon stopping"),await this.sessionStartChain.catch(()=>{}),this.portRefreshTimer&&(clearInterval(this.portRefreshTimer),this.portRefreshTimer=null),this.e1TtlRefreshTimer&&(clearInterval(this.e1TtlRefreshTimer),this.e1TtlRefreshTimer=null),this.toolActivity&&(await this.toolActivity.stop(),this.toolActivity=null);let e=Array.from(this.activeSessions.keys()),t=new Set,n=[];s.info(`Marking ${e.length} active session(s) as INACTIVE...`);for(let r of e){let o=this.activeSessions.get(r);o?.mobileEndWatcher&&(o.mobileEndWatcher.stop(),o.mobileEndWatcher=void 0),this.appSyncClient.stopHeartbeat(r)}for(let r of e){let o=this.activeSessions.get(r);try{if(o&&this.hasOtherLiveDaemonForSession(o.claudeSessionId)){s.info("Another daemon serves this session \u2014 skipping mark INACTIVE AND port file removal during shutdown",{sessionId:r,claudeSessionId:o.claudeSessionId,myPid:process.pid}),t.add(o.claudeSessionId);continue}try{await this.retireHostedSession(r),s.info("Session marked as INACTIVE during shutdown",{sessionId:r})}catch(c){n.push(r),s.error("Failed to mark session as INACTIVE during shutdown",{sessionId:r,error:c})}o&&this.removeOwnedSessionFiles(o.claudeSessionId)}catch(c){s.warn("Failed during shutdown cleanup",{sessionId:r,error:c})}}this.appSyncClient.cleanupSubscriptions(),this.activeSessions.clear(),await this.httpApi.stop({protectedSessionIds:t});let i=process.env[j];if(i&&t.size===0&&this.removeOwnedTmuxFiles(i),s.info("MCP Server stopped"),n.length>0)throw new Error(`Failed to retire hosted Claude sessions: ${n.join(", ")}`)}async retireHostedSession(e){let t;for(let n=1;n<=ve;n+=1)try{await this.appSyncClient.updateSession({sessionId:e,status:p.SessionStatus.INACTIVE});return}catch(i){t=i,s.warn("Failed to retire hosted Claude session",{sessionId:e,attempt:n,maxAttempts:ve,error:i instanceof Error?i.message:String(i)}),n<ve&&await new Promise(r=>setTimeout(r,Gt*n))}throw new Error(`Failed to retire hosted Claude session ${e} after ${ve} attempts: ${String(t)}`)}removeOwnedSessionFiles(e){this.removePortFile(e),this.removeOwnedFile(L.join(V.tmpdir(),`codevibe-claude-${e}.pid`)),this.removeOwnedFile(L.join(V.tmpdir(),`codevibe-claude-sent-uuids-${e}.txt`))}removeOwnedTmuxFiles(e){let t=/^codevibe-claude-(\d+)$/.exec(e);if(!t){s.warn("Refusing to remove Claude tmux lifecycle files for an unexpected session name",{tmuxSession:e});return}let n=t[1];this.removeOwnedFile(L.join(V.tmpdir(),`codevibe-claude-instance-${e}.json`)),this.removeOwnedFile(L.join(V.tmpdir(),`codevibe-claude-root-${e}.id`)),this.removeOwnedFile(L.join(V.tmpdir(),`codevibe-claude-tmux-session-${n}`))}removeOwnedFile(e){try{$.unlinkSync(e)}catch(t){t?.code!=="ENOENT"&&s.warn(`Failed to remove owned Claude lifecycle file: ${e}`,t)}}encryptForEmit(e,t,n,i){let r=this.activeSessions.get(i)?.encryptionSnapshot;if(!r)return s.error("No encryption snapshot for target session \u2014 dropping event (fail-closed, #638 H1)",{type:n,sessionId:i,sessionKnown:this.activeSessions.has(i)}),null;let o=(0,p.encryptForEmit)(r.sessionKey,r.sessionIsEncrypted,e,t,r.encryptionResolved);return o===null&&s.error("No session key for ENCRYPTED/unresolved session \u2014 dropping event (fail-closed, #638)",{type:n,sessionId:i,encryptionResolved:r.encryptionResolved}),o}async handleEventFromHook(e){let{session_id:t,hook_event_name:n,type:i,content:r}=e;if(s.info("Processing hook event",{sessionId:t,hookEvent:n,type:i}),n==="SessionStart"&&e.metadata?.source==="startup"&&this.currentClaudeSessionId!==null&&this.currentClaudeSessionId!==t&&this.nestedClaudeSessionIds.add(t),this.nestedClaudeSessionIds.has(t)){s.info("Ignoring hook from quarantined nested Claude session",{sessionId:t,hookEvent:n,owningSessionId:this.currentClaudeSessionId});return}let o,c;try{if(n==="SessionStart")await this.handleSessionStart(e);else if(n==="SessionEnd")await this.handleSessionEnd(e);else if(!this.activeSessions.has(this.claudeToBackendSessionId.get(t)||this.generateBackendSessionId(t))){s.warn("Hook event for un-bootstrapped session \u2014 self-healing via SessionStart (#638)",{claudeSessionId:t,hookEvent:n});let m=this.encryptedKeyRecovery?.claudeSessionId===t?this.encryptedKeyRecovery.backendSessionId:void 0;await this.handleSessionStart({session_id:t,metadata:e.metadata,transcript_path:e.transcript_path,received_at_ms:e.received_at_ms},m,"self-heal");let I=this.claudeToBackendSessionId.get(t)||this.generateBackendSessionId(t);if(!this.activeSessions.has(I)){s.warn("Dropping hook because Claude self-heal lifecycle intent is stale",{claudeSessionId:t,hookEvent:n});return}}let a=this.claudeToBackendSessionId.get(t)||this.generateBackendSessionId(t);if(n==="PostToolUse"&&e.metadata?.tool_name==="AskUserQuestion"){let m=this.activeSessions.get(a),I=this.computeAskUserQuestionFingerprint(e.tool_input?.questions),h=m?.pendingAskUserQuestionInvocations,y=I?h?.findIndex(v=>v.fingerprint===I)??-1:-1;if(m&&h&&y>=0){let[v]=h.splice(y,1);v.toolUseId=e.tool_use_id;let b=m.activeAskUserQuestionInvocation?.occurrenceId===v.occurrenceId;b&&(m.activeAskUserQuestionFingerprint&&(m.completedAskUserQuestionFingerprints||(m.completedAskUserQuestionFingerprints=new Set),m.completedAskUserQuestionFingerprints.add(m.activeAskUserQuestionFingerprint)),this.clearPromptState(m),v.completionEpoch=m.promptStateEpoch,m.activeAskUserQuestionInvocation=void 0);for(let E of v.e1PromptIds)await this.resolveE1PluginApproval(a,E);this.scheduleQueuedMobilePromptDrain(a),s.info("AskUserQuestion resolved by PostToolUse hook",{sessionId:a,occurrenceId:v.occurrenceId,toolUseId:v.toolUseId,promptIds:v.e1PromptIds,clearedCurrentPrompt:b})}else s.warn("Ignoring uncorrelated AskUserQuestion PostToolUse hook",{sessionId:a,toolUseId:e.tool_use_id,completionFingerprint:I?.slice(0,16)})}if(this.toolActivity?.isActive()&&(i===p.EventType.USER_PROMPT||i===p.EventType.ASSISTANT_RESPONSE||i===p.EventType.INTERACTIVE_PROMPT||i===p.EventType.NOTIFICATION)&&await this.toolActivity.flush().catch(m=>s.warn("[tool-activity] boundary flush failed",{sessionId:a,err:String(m)})),n==="UserPromptSubmit"){let m=this.activeSessions.get(a);if(m&&await this.clearInteractivePromptsForAcceptedInput(m),m?.completedAskUserQuestionFingerprints?.size){let I=m.completedAskUserQuestionFingerprints.size;m.completedAskUserQuestionFingerprints.clear(),s.info("Turn boundary \u2014 cleared closed-AskUserQuestion fingerprints",{sessionId:a,clearedCount:I})}}if(i===p.EventType.USER_PROMPT&&e.source===p.EventSource.DESKTOP&&n==="UserPromptSubmit"&&r&&this.isRecentMobilePrompt(a,r)){s.info("Skipping duplicate USER_PROMPT from mobile-originated prompt",{sessionId:a,contentLength:r.length});return}if(i===p.EventType.INTERACTIVE_PROMPT){(typeof e.prompt_id!="string"||e.prompt_id.length===0)&&(e.prompt_id=`synth-${(0,te.randomUUID)()}`,s.info("Synthesized prompt_id for INTERACTIVE_PROMPT (hook omitted it)",{sessionId:a,synthesizedPromptId:e.prompt_id}));let m=this.activeSessions.get(a),I;if(m&&e.metadata?.tool_name==="AskUserQuestion"){if(I=this.computeAskUserQuestionFingerprint(e.metadata.tool_input?.questions),I){let b=m.activeAskUserQuestionFingerprint===I,E=m.completedAskUserQuestionFingerprints?.has(I)??!1;if(b||E){s.info("Dropping duplicate INTERACTIVE_PROMPT \u2014 AskUserQuestion already tracked",{sessionId:a,fingerprint:I.slice(0,16),status:b?"in-flight":"completed",hookEvent:e.hook_event_name,promptId:e.prompt_id});return}}let h=e.metadata.tool_input?.questions,y=Array.isArray(h)&&h.some(b=>b?.multiSelect===!0),v=Array.isArray(h)&&(h[0]?.options?.length??0)===0;if(I&&(y||v)){m.completedAskUserQuestionFingerprints||(m.completedAskUserQuestionFingerprints=new Set),m.completedAskUserQuestionFingerprints.add(I);let b=y?"\u26A0\uFE0F This AskUserQuestion uses multi-select, which can't be answered from mobile. Please answer it on your desktop terminal.":"\u26A0\uFE0F This AskUserQuestion has no explicit options \u2014 please answer on your desktop terminal.";s.info("AUQ degraded at intercept \u2014 emitting notification, skipping walker setup",{sessionId:a,fingerprint:I.slice(0,16),hasMultiSelect:y,isZeroExplicitOption:v,questionCount:Array.isArray(h)?h.length:0}),setImmediate(()=>this.emitDegradedAUQNotification(a,b));let E=this.newE1Raise(a,!1),x=this.registerAskUserQuestionInvocation(m,I,this.nextPromptGen++,E.promptId);m.activeAskUserQuestionInvocation=x,this.emitSuppressedPromptCarrier(a,E).catch(F=>{s.error("E1: failed to emit degraded-AUQ badge carrier",{sessionId:a,promptId:E.promptId,error:F instanceof Error?F.message:String(F)})});return}}if(m){let h=this.newE1Raise(a,!0);e.prompt_id=h.promptId,this.clearPromptState(m),m.waitingForPromptResponse=!0,m.pendingPromptId=e.prompt_id;let y=this.nextPromptGen++;if(m.promptGenerationToken={promptId:e.prompt_id||"",gen:y},I){m.activeAskUserQuestionFingerprint=I;let v=this.registerAskUserQuestionInvocation(m,I,y,h.promptId);m.activeAskUserQuestionInvocation=v}s.info("Interactive prompt detected - will parse options from tmux",{sessionId:a,promptId:e.prompt_id,tokenGen:y,askUserQuestionFingerprint:I?.slice(0,16)})}this.promptPublications.run(e.prompt_id??"",()=>this.sendInteractivePromptAsync(a,e,r)).catch(h=>{s.error("Failed to send interactive prompt with dynamic options",{error:h})});return}if(i===p.EventType.TOOL_USE&&this.toolActivity?.isActive()&&await this.toolActivity.observeHookTool({toolUseId:e.tool_use_id,toolName:e.metadata?.tool_name,toolInput:e.tool_input})!=="closed")return;i===p.EventType.TOOL_USE&&e.tool_use_id&&this.sessionKey&&(c=String(e.tool_use_id),o=this.getToolActivityLegacyRecorder(a),o.markLegacyInFlight(c)),s.info("Hook event encryption state",{type:i,sessionId:a,hasSessionKey:!!this.sessionKey,sessionKeyLength:this.sessionKey?.length||0});let d=this.encryptForEmit(r,e.metadata,i,a);if(!d)return;let P=d.content,w=d.metadata,f=d.isEncrypted,g=await this.appSyncClient.createEvent({sessionId:a,type:i,source:e.source,content:P,metadata:w,promptId:e.prompt_id,timestamp:(0,p.prepareEventTimestamp)({orderingKey:a}),isEncrypted:f?!0:void 0});if(o&&c&&((0,p.isPersistedEventResult)(g)?await o.finalizeLegacyOwned([c]):o.clearLegacyInFlight(c)),i===p.EventType.USER_PROMPT&&e.source===p.EventSource.DESKTOP){let m=this.activeSessions.get(a);m?.waitingForPromptResponse&&(this.promoteFingerprintAndClearPromptState(m),s.info("Clearing prompt wait state - new desktop prompt received",{sessionId:a}))}s.debug("Event sent to AppSync successfully")}catch(a){throw o&&c&&o.clearLegacyInFlight(c),s.error("Failed to process hook event:",a),a}}buildToolActivityIntegration(){return new ye({logger:s,createEvent:e=>this.appSyncClient.createEvent(e),encryptContent:(e,t)=>p.cryptoService.encryptContent(e,t),encryptMetadata:(e,t)=>p.cryptoService.encryptMetadata(e,t),getSessionKey:()=>this.sessionKey,getSessionKeyGen:()=>this.sessionKeyGen,refreshSessionKey:e=>this.refreshToolActivitySessionKey(e),onTranscriptApiError:(e,t)=>this.relayTranscriptApiError(e,t)})}async relayTranscriptApiError(e,t){let n=nt(t),i={phase:"turn_error",notification_type:"api_error",claudeError:t.error??"unknown",transcriptUuid:t.uuid,...t.apiErrorStatus!==void 0?{apiErrorStatus:t.apiErrorStatus}:{},...t.apiErrorIsTransient!==void 0?{apiErrorIsTransient:t.apiErrorIsTransient}:{}},r=this.encryptForEmit(n,i,p.EventType.NOTIFICATION,e);return r?(await this.appSyncClient.createEvent({sessionId:e,type:p.EventType.NOTIFICATION,source:p.EventSource.DESKTOP,content:r.content,metadata:r.metadata,timestamp:(0,p.prepareEventTimestamp)({orderingKey:e}),isEncrypted:r.isEncrypted?!0:void 0}),s.info("Relayed Claude Code API error to mobile as NOTIFICATION",{sessionId:e,error:t.error??"unknown",apiErrorStatus:t.apiErrorStatus,transcriptUuid:t.uuid}),"delivered"):"dropped"}async refreshToolActivitySessionKey(e){try{let t=await this.appSyncClient.getSession(e);if(t&&t.status===p.SessionStatus.INACTIVE){let o=new Error(`SESSION_TERMINAL: session ${e} is ${t.status}; stop retrying`);throw o.errorType="SessionTerminal",o}if(!t||t.status!==p.SessionStatus.ACTIVE||!t.isEncrypted)return null;let n=t.sessionKeyGen??null,i=t.encryptedKeys??[];if(i.length===0)return null;let r=await p.keychainManager.getSessionKey(e,i,n);return r?(s.info("[tool-activity] refreshed session key for SessionKeyStale retry (live key untouched)",{sessionId:e,sessionKeyGen:n}),{sessionKey:r,sessionKeyGen:n}):null}catch(t){if((0,p.isTerminalSessionError)(t))throw t;return s.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 p.ToolActivityLedger({agent:"claude",sessionId:e,logger:s}),this.toolActivityLegacyRecorders.set(e,t)),t}handleSessionStart(e,t,n="explicit"){if(this.isStopping)return Promise.resolve();let i=e.session_id,r=n==="explicit"?this.recordExplicitClaudeSessionStart(i):{ownershipGeneration:this.claudeOwnershipGeneration,sessionIntentVersion:this.getClaudeSessionIntentVersion(i)};return this.enqueueSessionLifecycle(async()=>{if(this.isStopping)return;if(n==="self-heal"){if(!this.canSelfHealClaudeSession(i,r)){s.warn("Rejected stale Claude self-heal request",{claudeSessionId:i,currentClaudeSessionId:this.currentClaudeSessionId});return}this.currentClaudeSessionId===null&&(this.currentClaudeSessionId=i,r.ownershipGeneration=++this.claudeOwnershipGeneration)}else if(!this.isClaudeStartIntentCurrent(i,r))return;let o=++this.lifecycleGeneration;await this.handleSessionStartSerialized(e,t,o,()=>this.isClaudeStartIntentCurrent(i,r))})}getClaudeSessionIntentVersion(e){return this.claudeSessionIntentVersions.get(e)??0}bumpClaudeSessionIntentVersion(e){let t=this.getClaudeSessionIntentVersion(e)+1;return this.claudeSessionIntentVersions.set(e,t),t}recordExplicitClaudeSessionStart(e){this.clearEncryptedKeyRecovery("explicit Claude SessionStart");let t=this.currentClaudeSessionId;t&&t!==e&&this.endedOrSupersededClaudeSessionIds.add(t);for(let n of this.activeSessions.values())n.claudeSessionId!==e&&this.endedOrSupersededClaudeSessionIds.add(n.claudeSessionId);return this.currentClaudeSessionId=e,this.endedOrSupersededClaudeSessionIds.delete(e),{ownershipGeneration:++this.claudeOwnershipGeneration,sessionIntentVersion:this.bumpClaudeSessionIntentVersion(e)}}recordExplicitClaudeSessionEnd(e){this.clearEncryptedKeyRecovery("explicit Claude SessionEnd",e),this.endedOrSupersededClaudeSessionIds.add(e),this.bumpClaudeSessionIntentVersion(e),this.currentClaudeSessionId===e&&(this.currentClaudeSessionId=null,this.claudeOwnershipGeneration+=1)}isClaudeStartIntentCurrent(e,t){return!this.isStopping&&this.currentClaudeSessionId===e&&this.claudeOwnershipGeneration===t.ownershipGeneration&&this.getClaudeSessionIntentVersion(e)===t.sessionIntentVersion&&!this.endedOrSupersededClaudeSessionIds.has(e)}canSelfHealClaudeSession(e,t){return this.endedOrSupersededClaudeSessionIds.has(e)||this.getClaudeSessionIntentVersion(e)!==t.sessionIntentVersion||this.claudeOwnershipGeneration!==t.ownershipGeneration||this.currentClaudeSessionId&&this.currentClaudeSessionId!==e?!1:!Array.from(this.activeSessions.values()).some(n=>n.claudeSessionId!==e)}enqueueSessionLifecycle(e){let t=this.sessionStartChain;this.sessionLifecycleQueueDepth+=1;let n=this.sessionLifecycleQueueDepth===1?e():t.catch(()=>{}).then(e),i=n.finally(()=>{this.sessionLifecycleQueueDepth-=1});return this.sessionStartChain=i.catch(()=>{}),n}async handleSessionStartSerialized(e,t,n=this.lifecycleGeneration,i=()=>!0){let r=()=>this.isLifecycleCurrent(n)&&i();if(!r())return;let o=e.session_id,c=this.appSyncClient.getCurrentUserId(),a=this.claudeToBackendSessionId.get(o),l=t||a?null:await this.sessionBindingStore.read(c,o);if(!r())return;let d=t??a??l??this.generateBackendSessionId(o),P=e.metadata?.cwd||process.cwd();this.claudeToBackendSessionId.set(o,d),s.info("Session started",{claudeSessionId:o,sessionId:d,cwd:P});let w=this.activeSessions.get(d);if(w){if(w.mobileEndWatcher?.stop(),w.mobileEndWatcher=void 0,w.orchestrationPromptWatcher?.stop(),w.orchestrationPromptWatcher=void 0,w.orchestrationDecisionWatcher?.stop(),w.orchestrationDecisionWatcher=void 0,w.orchestrationClient){try{await w.orchestrationClient.destroy()}catch(h){s.warn("[v1-bridge] OrchestrationClient.destroy() failed on same-session restart",{sessionId:d,error:String(h)})}w.orchestrationClient=void 0}this.appSyncClient.stopHeartbeat(d),this.appSyncClient.cleanupSubscription(d),this.activeSessions.get(d)===w&&this.activeSessions.delete(d)}let f=Array.from(this.activeSessions.keys()).filter(h=>h!==d);if(f.length>0){s.info(`Marking ${f.length} previous session(s) as INACTIVE`);for(let h of f){if(this.toolActivity&&this.toolActivity.sessionId===h){let v=this.toolActivity,b=!1;for(let E=1;E<=2&&!b;E+=1)try{await v.stop(),b=!0}catch(x){s.warn("[tool-activity] outgoing consolidator drain failed on resume",{prevId:h,attempt:E,error:String(x)})}b||s.error("[api-error-relay] outgoing drain failed twice on resume \u2014 discarding; an uncheckpointed API error of the outgoing session may be lost (residual R7)",{prevId:h}),this.toolActivity=null}let y=this.activeSessions.get(h);if(y?.mobileEndWatcher&&(y.mobileEndWatcher.stop(),y.mobileEndWatcher=void 0),y?.orchestrationPromptWatcher&&(y.orchestrationPromptWatcher.stop(),y.orchestrationPromptWatcher=void 0),y?.orchestrationDecisionWatcher&&(y.orchestrationDecisionWatcher.stop(),y.orchestrationDecisionWatcher=void 0),y?.orchestrationClient){try{await y.orchestrationClient.destroy()}catch(v){s.warn("[v1-bridge] OrchestrationClient.destroy() failed on resume",{prevId:h,error:String(v)})}y.orchestrationClient=void 0}this.appSyncClient.stopHeartbeat(h),this.appSyncClient.cleanupSubscription(h);try{await this.appSyncClient.updateSession({sessionId:h,status:p.SessionStatus.INACTIVE}),s.info("Previous session marked INACTIVE",{prevId:h,newSessionId:d})}catch(v){s.warn("Failed to mark previous session as INACTIVE",{prevId:h,error:v})}y&&this.removePortFile(y.claudeSessionId),this.activeSessions.delete(h)}}this.writePortFile(o);let g={sessionId:d,claudeSessionId:o,userId:c,projectPath:P,cwd:P,createdAt:new Date,subscriptionActive:!1,waitingForPromptResponse:!1,promptStateEpoch:0,metadata:e.metadata||{},transcriptPath:e.transcript_path,originatedDecisions:new Set,externallyResolvedGateIds:new Set};this.encryptionResolved=!1,g.encryptionSnapshot={sessionKey:null,sessionIsEncrypted:!1,encryptionResolved:!1},this.activeSessions.set(d,g);try{let h;try{h=await(0,p.resumeOrCreateSession)({sessionId:d,userId:g.userId,agentType:p.AgentType.CLAUDE,projectPath:P,metadata:e.metadata||{}},this.appSyncClient,s)}catch(y){if(!(0,p.isRetiredSessionCannotReactivateError)(y))throw y;if(!r()||this.activeSessions.get(d)!==g){this.activeSessions.get(d)===g&&this.activeSessions.delete(d),this.claudeToBackendSessionId.get(o)===d&&this.claudeToBackendSessionId.delete(o),this.removePortFile(o);return}let v=d;this.appSyncClient.stopHeartbeat(v),this.appSyncClient.cleanupSubscription(v),this.activeSessions.delete(v);let b=ke(c,o,v);if(await this.sessionBindingStore.write(c,o,b),!r())return;d=b,g.sessionId=d,this.activeSessions.set(d,g),this.claudeToBackendSessionId.set(o,d),s.warn("Backend rejected retired Claude generation during SessionStart; creating replacement row",{claudeSessionId:o,retiredSessionId:v,replacementSessionId:d}),h=await(0,p.resumeOrCreateSession)({sessionId:d,userId:g.userId,agentType:p.AgentType.CLAUDE,projectPath:P,metadata:e.metadata||{}},this.appSyncClient,s)}if(!r()){await this.retireCancelledClaudeBootstrap({requestedSessionId:d,authoritativeSessionId:h.sessionId,claudeSessionId:o,sessionState:g});return}if(this.activeSessions.get(d)!==g){s.warn("Ignoring superseded Claude session bootstrap completion",{claudeSessionId:o,requestedSessionId:d,authoritativeSessionId:h.sessionId}),await this.retireCancelledClaudeBootstrap({requestedSessionId:d,authoritativeSessionId:h.sessionId,claudeSessionId:o,sessionState:g});return}if(h.sessionId!==d){if(await this.sessionBindingStore.write(c,o,h.sessionId),!r()){await this.retireCancelledClaudeBootstrap({requestedSessionId:d,authoritativeSessionId:h.sessionId,claudeSessionId:o,sessionState:g});return}this.activeSessions.get(d)===g&&this.activeSessions.delete(d),d=h.sessionId,g.sessionId=d,this.activeSessions.set(d,g),this.claudeToBackendSessionId.set(o,d),s.info("Bound Claude session to recovered backend identity",{claudeSessionId:o,sessionId:d})}if(this.sessionKey=h.sessionKey,this.sessionKeyGen=h.sessionKeyGen,this.sessionIsEncrypted=!!h.sessionKey,this.encryptionResolved=!0,g.encryptionSnapshot={sessionKey:h.sessionKey,sessionIsEncrypted:!!h.sessionKey,encryptionResolved:!0},this.toolActivity&&(await this.toolActivity.stop(),this.toolActivity=null,!r())){await this.retireCancelledClaudeBootstrap({requestedSessionId:d,authoritativeSessionId:h.sessionId,claudeSessionId:o,sessionState:g});return}if(this.sessionKey){let y=this.buildToolActivityIntegration();try{if(await y.start(d,e.transcript_path,{hostingSinceMs:e.received_at_ms}),!r()){await y.stop().catch(()=>{}),await this.retireCancelledClaudeBootstrap({requestedSessionId:d,authoritativeSessionId:h.sessionId,claudeSessionId:o,sessionState:g});return}this.toolActivity=y}catch(v){s.warn("[tool-activity] consolidator start failed \u2014 keeping per-tool TOOL_USE",{sessionId:d,error:String(v)}),this.toolActivity=null,await y.stop().catch(()=>{})}}}catch(h){if(!r()){await this.retireCancelledClaudeBootstrap({requestedSessionId:d,authoritativeSessionId:this.authoritativeSessionIdFromError(h)??d,claudeSessionId:o,sessionState:g});return}let y=h?.authoritativeSessionId;if(typeof y=="string"&&y.length>0&&y!==d&&this.activeSessions.get(d)===g&&(this.activeSessions.delete(d),d=y,g.sessionId=d,this.activeSessions.set(d,g),this.claudeToBackendSessionId.set(o,d),s.info("Bound keyless Claude recovery to authoritative backend identity",{claudeSessionId:o,sessionId:d})),this.isSessionLimitExceeded(h)){this.displaySubscriptionLimitError(h,"session"),this.activeSessions.get(d)===g&&this.activeSessions.delete(d),this.removePortFile(o);return}if(h?.code==="ENCRYPTED_SESSION_NO_KEY"){this.sessionIsEncrypted=!0,this.sessionKey=null,this.encryptionResolved=!0,g.encryptionSnapshot={sessionKey:null,sessionIsEncrypted:!0,encryptionResolved:!0};let v=this.enterEncryptedKeyRecovery(e,d);this.appSyncClient.cleanupSubscription(d),await this.invalidateEncryptedRecoveryLiveness(v),s.error("Failed to create/resume session:",h);return}else{this.activeSessions.get(d)===g&&this.activeSessions.delete(d),s.error("Failed to create/resume session \u2014 entry removed so the next hook event re-runs bootstrap via self-heal (#638 G1):",h);return}}if(!r()){await this.retireCancelledClaudeBootstrap({requestedSessionId:d,authoritativeSessionId:d,claudeSessionId:o,sessionState:g});return}try{await this.recoverQueuedMobilePrompts(d)}catch(h){s.error("Durable mobile-prompt queue recovery failed closed",{sessionId:d,error:String(h)}),await this.sendPromptError(d,"A previously queued mobile message could not be recovered safely. Please inspect the desktop before retrying it.").catch(()=>{}),this.activeSessions.get(d)===g&&this.activeSessions.delete(d);return}this.subscribeToMobileEvents(d);try{await this.drainParkedE1Orphans(o,d,r)}catch(h){s.warn("E1: parked-orphan drain threw (non-fatal; TTL drain will retry)",{sessionId:d,error:h instanceof Error?h.message:String(h)})}if(!await this.appSyncClient.startHeartbeat(d)){s.error("Initial Claude session heartbeat was not persisted",{sessionId:d});let h=this.encryptedKeyRecovery;if(h?.claudeSessionId===o&&h.backendSessionId===d){let y=this.enterEncryptedKeyRecovery(e,d);this.appSyncClient.cleanupSubscription(d),await this.invalidateEncryptedRecoveryLiveness(y);return}}if(!r()){await this.retireCancelledClaudeBootstrap({requestedSessionId:d,authoritativeSessionId:d,claudeSessionId:o,sessionState:g});return}let I=this.activeSessions.get(d);if(I){I.mobileEndWatcher=this.appSyncClient.watchForMobileEnd(d,async()=>{s.info("Mobile ended session \u2014 sending desktop quit",{sessionId:d}),this.appSyncClient.stopHeartbeat(d),this.appSyncClient.cleanupSubscription(d);try{await this.appSyncClient.updateSession({sessionId:d,status:p.SessionStatus.INACTIVE}),s.info("Marked session INACTIVE on mobile-end (before tmux terminate)",{sessionId:d})}catch(v){s.warn("Failed to mark session INACTIVE on mobile-end",{sessionId:d,error:v})}let y=process.env[j];if(!y){s.warn("No tmux session set; skipping desktop self-terminate",{sessionId:d,expectedEnv:j});return}await jt(y,$t)});let h=!1;try{I.orchestrationPromptWatcher=this.subscribeToOrchestrationPrompts(d)}catch(y){h=!0,s.warn("[v1-bridge] Failed to start orchestration prompt watcher",{sessionId:d,error:String(y)})}try{I.orchestrationDecisionWatcher=await this.subscribeToApplyUserDecision(I)}catch(y){h=!0,s.warn("[v1-bridge] Failed to start applyUserDecision watcher",{sessionId:d,error:String(y)})}h&&this.emitBridgeUnavailableBanner()}this.clearEncryptedKeyRecovery("session key recovered and liveness established",o,d)}recoverRetiredBackendSession(e,t){let n=this.retiredSessionRecoveryPromises.get(e);if(n)return n;if(this.isStopping)return Promise.resolve();let i=t.claudeSessionId,r={ownershipGeneration:this.claudeOwnershipGeneration,sessionIntentVersion:this.getClaudeSessionIntentVersion(i)},o=()=>this.isClaudeStartIntentCurrent(i,r),a=this.enqueueSessionLifecycle(async()=>{if(this.isStopping||!o()||this.activeSessions.get(e)!==t||this.claudeToBackendSessionId.get(t.claudeSessionId)!==e)return;let l={session_id:t.claudeSessionId,metadata:t.metadata,transcript_path:t.transcriptPath};s.warn("Backend retired live Claude session; creating replacement row",{sessionId:e,claudeSessionId:t.claudeSessionId});let d=await this.snapshotSettledOpenE1(e);if(this.isStopping||!o()||this.activeSessions.get(e)!==t)return;this.parkE1Orphans(t.claudeSessionId,d);let P=ke(t.userId,t.claudeSessionId,e);if(await this.sessionBindingStore.write(t.userId,t.claudeSessionId,P),!o())return;let w=++this.lifecycleGeneration;await this.handleSessionEndSerialized({session_id:t.claudeSessionId,metadata:{reason:"backend-generation-retired"}}),!(!this.isLifecycleCurrent(w)||!o())&&await this.handleSessionStartSerialized(l,P,w,o)}).finally(()=>{this.retiredSessionRecoveryPromises.get(e)===a&&this.retiredSessionRecoveryPromises.delete(e)});return this.retiredSessionRecoveryPromises.set(e,a),a}isLifecycleCurrent(e){return!this.isStopping&&this.lifecycleGeneration===e}authoritativeSessionIdFromError(e){let t=e?.authoritativeSessionId;return typeof t=="string"&&t.length>0?t:null}async retireCancelledClaudeBootstrap(e){let t=new Set([e.requestedSessionId,e.authoritativeSessionId]);for(let n of t)this.appSyncClient.stopHeartbeat(n),this.appSyncClient.cleanupSubscription(n);this.activeSessions.get(e.requestedSessionId)===e.sessionState&&this.activeSessions.delete(e.requestedSessionId),this.activeSessions.get(e.authoritativeSessionId)===e.sessionState&&this.activeSessions.delete(e.authoritativeSessionId),this.claudeToBackendSessionId.get(e.claudeSessionId)===e.requestedSessionId&&this.claudeToBackendSessionId.delete(e.claudeSessionId),this.removePortFile(e.claudeSessionId);try{await this.appSyncClient.updateSession({sessionId:e.authoritativeSessionId,status:p.SessionStatus.INACTIVE}),s.info("Cancelled Claude bootstrap row marked INACTIVE",{sessionId:e.authoritativeSessionId,claudeSessionId:e.claudeSessionId})}catch(n){s.warn("Cancelled Claude bootstrap row could not be marked INACTIVE",{sessionId:e.authoritativeSessionId,error:n instanceof Error?n.message:String(n)})}}async emitBridgeUnavailableBanner(){let e=process.env[j],t="[CodeVibe] Cross-device decision sync unavailable \u2014 mobile responses will be sent as free-text";if(s.warn(t),!!e)try{await we(`tmux display-message -t "${e}" -- ${JSON.stringify(t)}`)}catch(n){s.warn("[v1-bridge] tmux display-message for bridge-unavailable banner failed",{tmuxSession:e,error:String(n)})}}handleSessionEnd(e){return this.isStopping?Promise.resolve():(this.recordExplicitClaudeSessionEnd(e.session_id),this.enqueueSessionLifecycle(()=>this.handleSessionEndSerialized(e)))}async handleSessionEndSerialized(e){let t=e.session_id,n=this.claudeToBackendSessionId.get(t)||this.generateBackendSessionId(t);s.info("Session ended",{claudeSessionId:t,sessionId:n,reason:e.metadata?.reason});let i=this.activeSessions.get(n);if(i?.mobileEndWatcher&&(i.mobileEndWatcher.stop(),i.mobileEndWatcher=void 0),i?.orchestrationPromptWatcher&&(i.orchestrationPromptWatcher.stop(),i.orchestrationPromptWatcher=void 0),i?.orchestrationDecisionWatcher&&(i.orchestrationDecisionWatcher.stop(),i.orchestrationDecisionWatcher=void 0),i?.orchestrationClient){try{await i.orchestrationClient.destroy()}catch(c){s.warn("[v1-bridge] OrchestrationClient.destroy() failed",{sessionId:n,error:String(c)})}i.orchestrationClient=void 0}this.toolActivity&&this.toolActivity.sessionId===n&&(await this.toolActivity.stop(),this.toolActivity=null),this.activeSessions.delete(n),this.claudeToBackendSessionId.delete(t),this.removePortFile(t),i?.waitingForPromptResponse&&(s.info("Clearing prompt wait state - session ending",{sessionId:n}),this.clearPromptState(i));let r=this.queuedMobilePromptTimers.get(n);r&&clearTimeout(r),this.queuedMobilePromptTimers.delete(n),this.queuedMobilePrompts.delete(n);let o=this.toolActivityLegacyRecorders.get(n);if(o&&o.disposeLegacyCoordination(),this.toolActivityLegacyRecorders.delete(n),this.appSyncClient.stopHeartbeat(n),this.appSyncClient.cleanupSubscription(n),i)try{await this.appSyncClient.updateSession({sessionId:n,status:p.SessionStatus.INACTIVE}),s.info("Session marked as INACTIVE in AppSync",{sessionId:n})}catch(c){s.warn("Failed to update session in AppSync:",c)}else s.warn("Cannot update session - session state not found",{sessionId:n});s.debug("Session cleanup completed",{sessionId:n})}async withMobilePromptAdmission(e,t){let n,r=(this.queuedMobilePromptAdmissions.get(e)??Promise.resolve()).catch(()=>{}).then(async()=>{n=await t()});this.queuedMobilePromptAdmissions.set(e,r);try{return await r,n}finally{this.queuedMobilePromptAdmissions.get(e)===r&&this.queuedMobilePromptAdmissions.delete(e)}}recoverQueuedMobilePrompts(e){let t=this.activeSessions.get(e);if(!t)throw new Error(`Cannot recover mobile queue for inactive session ${e}`);return this.withMobilePromptAdmission(t.claudeSessionId,()=>this.recoverQueuedMobilePromptsOnce(e,t.claudeSessionId))}async recoverQueuedMobilePromptsOnce(e,t){let n=this.claudeToBackendSessionId.get(t)??e,i=this.activeSessions.get(n);if(!i||i.claudeSessionId!==t)throw new Error(`Cannot recover mobile queue for inactive native session ${t}`);let r=await this.mobilePromptQueueStore.read(t);for(let l of r)l.sessionId!==n&&(l.sessionId=n,await this.mobilePromptQueueStore.put(t,l)),l.state==="sending"&&(l.state="terminal",l.lastError="CodeVibe restarted while sending this message. It was not retried to avoid submitting it twice.",await this.mobilePromptQueueStore.put(t,l));let o=this.queuedMobilePrompts.get(n)??[],c=new Map;for(let l of[...r,...o])c.set(l.event.eventId,l);let a=[...c.values()];if(a.length>0){this.queuedMobilePrompts.set(n,a),i.processedEventIds||(i.processedEventIds=new Set);for(let l of a)i.processedEventIds.add(l.event.eventId);this.scheduleQueuedMobilePromptDrain(n)}}async enqueueMobilePrompt(e,t,n,i){let r=i??this.activeSessions.get(e)?.claudeSessionId;if(!r)throw new Error(`Cannot identify native mobile queue for ${e}`);return this.withMobilePromptAdmission(r,()=>this.enqueueMobilePromptOnce(e,r,t,n))}async enqueueMobilePromptOnce(e,t,n,i){let r=this.claudeToBackendSessionId.get(t)??e,o=this.activeSessions.get(r),c=this.queuedMobilePrompts.get(r)??await this.mobilePromptQueueStore.read(t),a=c.find(d=>d.event.eventId===n.eventId);if(a){let d=a.sessionId!==r;return a.sessionId=r,i&&!a.pendingDismissal&&(a.pendingDismissal=i,d=!0),d&&await this.mobilePromptQueueStore.put(t,a),o&&(o.processedEventIds||(o.processedEventIds=new Set),o.processedEventIds.add(n.eventId),this.queuedMobilePrompts.set(r,c),i||this.scheduleQueuedMobilePromptDrain(r)),a}let l={version:2,nativeSessionId:t,sessionId:r,event:n,state:"queued",attempts:0,nextAttemptAt:Date.now(),pendingDismissal:i};return await this.mobilePromptQueueStore.put(t,l),c.push(l),o&&this.activeSessions.get(r)===o&&(this.queuedMobilePrompts.set(r,c),o.processedEventIds||(o.processedEventIds=new Set),o.processedEventIds.add(n.eventId),i||this.scheduleQueuedMobilePromptDrain(r)),l}scheduleQueuedMobilePromptDrain(e,t=0){let n=this.queuedMobilePromptTimers.get(e);n&&clearTimeout(n);let i=setTimeout(()=>{this.queuedMobilePromptTimers.delete(e),this.drainQueuedMobilePrompts(e)},Math.max(0,t));i.unref?.(),this.queuedMobilePromptTimers.set(e,i)}queuedPromptBackoffMs(e){return Math.min(u.MOBILE_PROMPT_QUEUE_MAX_BACKOFF_MS,500*2**Math.max(0,e-1))}async terminalizeQueuedMobilePrompt(e,t){try{return t.terminalNoticeSent||(await this.sendPromptError(e.sessionId,t.lastError??"The queued message could not be sent. Please retry it."),t.terminalNoticeSent=!0,await this.mobilePromptQueueStore.put(t.nativeSessionId,t)),await this.acknowledgeQueuedMobilePromptExecuted(e,t)?(await this.mobilePromptQueueStore.remove(t.nativeSessionId,t.event.eventId),!0):!1}catch(n){return s.warn("Failed to publish queued mobile-prompt terminal disposition",{sessionId:e.sessionId,eventId:t.event.eventId,error:String(n)}),!1}}async acknowledgeQueuedMobilePromptExecuted(e,t){try{let n=await this.appSyncClient.updateEventStatus({eventId:t.event.eventId,sessionId:t.event.sessionId,timestamp:t.event.timestamp,deliveryStatus:p.DeliveryStatus.EXECUTED});return n?.eventId===t.event.eventId&&n?.sessionId===t.event.sessionId&&n?.timestamp===t.event.timestamp&&n?.deliveryStatus===p.DeliveryStatus.EXECUTED?(e.processedEventIds||(e.processedEventIds=new Set),e.processedEventIds.add(t.event.eventId),!0):(s.warn("Queued mobile-prompt EXECUTED acknowledgement was not exact; retaining tombstone",{sessionId:e.sessionId,sourceSessionId:t.event.sessionId,eventId:t.event.eventId,acknowledgedEventId:n?.eventId,acknowledgedSessionId:n?.sessionId,acknowledgedTimestamp:n?.timestamp,acknowledgedDeliveryStatus:n?.deliveryStatus}),!1)}catch(n){return s.warn("Queued mobile-prompt EXECUTED acknowledgement failed; retaining tombstone",{sessionId:e.sessionId,sourceSessionId:t.event.sessionId,eventId:t.event.eventId,state:t.state,error:String(n)}),!1}}async reconcileQueuedPromptDismissal(e,t){let n=t.pendingDismissal;if(!n)return"resolved";let i=n.invocation.occurrenceId,r=e.pendingAskUserQuestionInvocations?.some(c=>c.occurrenceId===i)??!1,o=e.activeAskUserQuestionInvocation?.occurrenceId;return!r&&o!==i?(n.dismissalSent&&await this.settleDismissedAskUserQuestionOccurrence(e,n.invocation),t.pendingDismissal=void 0,await this.mobilePromptQueueStore.put(t.nativeSessionId,t),"resolved"):o!==i||e.pendingPanePromptIdentity?.hookOccurrenceId!==i?(o&&o!==i&&await this.settleDismissedAskUserQuestionOccurrence(e,n.invocation),t.pendingDismissal=void 0,await this.mobilePromptQueueStore.put(t.nativeSessionId,t),"resolved"):n.nextAttemptAt>Date.now()?(this.scheduleQueuedMobilePromptDrain(e.sessionId,n.nextAttemptAt-Date.now()),"retry"):await this.resolveAskUserQuestionAfterVerifiedDismissal(e,n.expectedIdentity,n.invocation,{attempts:1,delayMs:0})?(t.pendingDismissal=void 0,await this.mobilePromptQueueStore.put(t.nativeSessionId,t),"resolved"):(n.attempts+=1,n.attempts>=u.MOBILE_PROMPT_QUEUE_MAX_ATTEMPTS?(t.pendingDismissal=void 0,t.state="terminal",t.lastError="The desktop chooser did not close after dismissal, so this message was not submitted. Please retry it.",await this.mobilePromptQueueStore.put(t.nativeSessionId,t),"terminal"):(n.nextAttemptAt=Date.now()+this.queuedPromptBackoffMs(n.attempts),t.lastError="Waiting for the dismissed desktop chooser to close",await this.mobilePromptQueueStore.put(t.nativeSessionId,t),this.scheduleQueuedMobilePromptDrain(e.sessionId,n.nextAttemptAt-Date.now()),"retry"))}async drainQueuedMobilePrompts(e){if(this.queuedMobilePromptDrains.has(e))return;let t=this.activeSessions.get(e),n=this.queuedMobilePrompts.get(e);if(!(!t||!n?.length)){this.queuedMobilePromptDrains.add(e);try{for(;n.length>0;){if(this.activeSessions.get(e)!==t)return;let i=n[0];if(i.state==="submitted"){if(!await this.acknowledgeQueuedMobilePromptExecuted(t,i)){this.scheduleQueuedMobilePromptDrain(e,2e3);return}await this.mobilePromptQueueStore.remove(i.nativeSessionId,i.event.eventId),n.shift();continue}if(i.state==="terminal"){if(!await this.terminalizeQueuedMobilePrompt(t,i)){this.scheduleQueuedMobilePromptDrain(e,2e3);return}n.shift();continue}let r=await this.reconcileQueuedPromptDismissal(t,i);if(r==="retry")return;if(r==="terminal")continue;if(t.activeAskUserQuestionInvocation||t.waitingForPromptResponse||t.promptGenerationToken!==void 0){this.scheduleQueuedMobilePromptDrain(e,500);return}let o=i.nextAttemptAt-Date.now();if(o>0){this.scheduleQueuedMobilePromptDrain(e,o);return}if(!await this.waitForChooserAbsence(e,t,{attempts:1,delayMs:0})){i.attempts+=1,i.attempts>=u.MOBILE_PROMPT_QUEUE_MAX_ATTEMPTS?(i.state="terminal",i.lastError="The desktop chooser did not close, so this message was not submitted. Please retry it."):(i.nextAttemptAt=Date.now()+this.queuedPromptBackoffMs(i.attempts),i.lastError="Waiting for the desktop chooser to close"),await this.mobilePromptQueueStore.put(i.nativeSessionId,i);continue}if(i.state="sending",i.lastError=void 0,await this.mobilePromptQueueStore.put(i.nativeSessionId,i),await this.executeMobilePrompt(e,i.event,{suppressFailureNotification:!0,deferStatusUpdate:!0})){i.state="submitted",await this.mobilePromptQueueStore.put(i.nativeSessionId,i);continue}i.state="terminal",i.lastError="CodeVibe could not prove whether this message reached Claude. It was not retried to avoid submitting it twice.",await this.mobilePromptQueueStore.put(i.nativeSessionId,i)}this.queuedMobilePrompts.delete(e)}finally{this.queuedMobilePromptDrains.delete(e),n.length>0&&!this.queuedMobilePromptTimers.has(e)&&this.scheduleQueuedMobilePromptDrain(e,500)}}}subscribeToMobileEvents(e){s.info("Subscribing to mobile events",{sessionId:e});let t=this.activeSessions.get(e);if(!t){s.error("Session not found",{sessionId:e});return}this.appSyncClient.subscribeToEvents(e,async n=>this.dispatchMobileEvent(e,n),n=>{s.error("Subscription error",{sessionId:e,error:n})},{onSessionRetired:()=>this.recoverRetiredBackendSession(e,t)}),t.subscriptionActive=!0,s.info("Subscription active",{sessionId:e})}async dispatchMobileEvent(e,t){s.info("Received mobile event",{eventId:t.eventId,type:t.type,sessionId:t.sessionId,isEncrypted:t.isEncrypted});let n=t.content||"";if(t.isEncrypted&&this.sessionKey)try{n=p.cryptoService.decryptContent(t.content,this.sessionKey),s.debug("Event decrypted successfully",{eventId:t.eventId})}catch(g){s.error("Failed to decrypt event:",{eventId:t.eventId,error:g}),n=t.content}let i,r,o=!1,c,a,l,d,P;if(t.type===p.EventType.USER_PROMPT||t.type===p.EventType.PROMPT_RESPONSE)if(r=this.activeSessions.get(e),P=(0,p.classifyMobilePromptInput)(n,Object.keys(r?.pendingSubmitMap??{})),!r)i="no-session";else if(r.processedEventIds?.has(t.eventId))i="skip-dedup";else if(r.inFlightEventIds?.has(t.eventId))i="drop-event-redeliver";else if(r.waitingForPromptResponse){let g=r.promptGenerationToken;if(!g)i="regular";else if(t.type===p.EventType.USER_PROMPT&&P.kind==="option"&&r.hasReceivedPromptResponse&&(!t.promptId||t.promptId.length===0))i="drop-stale-answer";else if(P.kind==="option"&&t.type===p.EventType.PROMPT_RESPONSE&&(!t.promptId||g.promptId.length===0||t.promptId!==g.promptId))i="drop-stale-answer";else if(P.kind==="option"&&t.promptId&&g.promptId.length>0&&t.promptId!==g.promptId)i="drop-stale-answer";else{let m=g.promptId.length>0?g.promptId:`__prompt_gen_${g.gen}`;r.inFlightPromptIds?.has(m)?i="drop-in-flight":(r.inFlightPromptIds||(r.inFlightPromptIds=new Set),r.inFlightEventIds||(r.inFlightEventIds=new Set),r.inFlightPromptIds.add(m),r.inFlightEventIds.add(t.eventId),o=!0,l=m,d=t.eventId,c={promptId:g.promptId,gen:g.gen},a={pendingPromptId:r.pendingPromptId,pendingSubmitMap:r.pendingSubmitMap?{...r.pendingSubmitMap}:void 0,pendingPanePromptIdentity:r.pendingPanePromptIdentity,activeAskUserQuestionInvocation:r.activeAskUserQuestionInvocation},i="walker")}}else i="regular";else i="not-user-prompt";let f={...t,content:n};if(i!=="skip-dedup")try{await this.appSyncClient.updateEventStatus({eventId:t.eventId,sessionId:t.sessionId,timestamp:t.timestamp,deliveryStatus:p.DeliveryStatus.DELIVERED}),s.info("Event marked as DELIVERED",{eventId:t.eventId})}catch(g){s.warn("Failed to mark event as DELIVERED",{eventId:t.eventId,error:g})}if(t.type===p.EventType.USER_PROMPT&&r?.pendingOrchestrationGate&&await this.handleOrchestrationPromptResponse(r,n,t)){l&&r.inFlightPromptIds?.delete(l),d&&r.inFlightEventIds?.delete(d);return}if(i==="skip-dedup"){s.info("[walker] Subscription-level dedup hit (already processed) \u2014 skipping",{sessionId:e,eventId:t.eventId});return}if(i==="drop-stale-answer"){s.info("[walker] Stale answer dropped \u2014 event.promptId does not match current pending promptId",{sessionId:e,eventId:t.eventId,eventPromptId:t.promptId,currentPromptId:r?.promptGenerationToken?.promptId}),r&&(r.processedEventIds||(r.processedEventIds=new Set),r.processedEventIds.add(t.eventId));try{await this.markEventExecuted(t)}catch(g){s.warn("[walker] markEventExecuted threw on stale-answer drop \u2014 relying on processedEventIds Set",{sessionId:e,eventId:t.eventId,error:String(g)})}return}if(i==="drop-in-flight"){s.warn("[walker] Subscription-level in-flight guard \u2014 dropping duplicate USER_PROMPT (different eventId, same prompt)",{sessionId:e,eventId:t.eventId}),r&&(r.processedEventIds||(r.processedEventIds=new Set),r.processedEventIds.add(t.eventId));try{await this.markEventExecuted(t)}catch(g){s.warn("[walker] markEventExecuted threw on subscription-level duplicate drop \u2014 relying on processedEventIds Set",{sessionId:e,eventId:t.eventId,error:String(g)})}return}if(i==="drop-event-redeliver"){s.info("[walker] Subscription-level event-level redelivery \u2014 silent skip (original still in flight)",{sessionId:e,eventId:t.eventId});return}if(i==="walker"){t.type===p.EventType.PROMPT_RESPONSE&&P?.kind==="option"&&r&&(r.hasReceivedPromptResponse=!0),await this.handleMobilePromptResponse(e,t,n,r,o,c,l,d,a);return}if(i==="regular"){if(t.type===p.EventType.PROMPT_RESPONSE&&P?.kind==="option"){s.warn("Received PROMPT_RESPONSE with no active walker \u2014 dropping",{sessionId:e,eventId:t.eventId,promptId:t.promptId}),r&&(r.processedEventIds||(r.processedEventIds=new Set),r.processedEventIds.add(t.eventId));try{await this.markEventExecuted(t)}catch(g){s.warn("markEventExecuted threw on PROMPT_RESPONSE orphan drop \u2014 relying on processedEventIds Set",{sessionId:e,eventId:t.eventId,error:String(g)})}return}await this.executeMobilePrompt(e,f);return}if(i==="no-session"){s.warn("Received mobile prompt input for unknown session \u2014 ignoring",{sessionId:e,eventId:t.eventId,type:t.type});return}}async handleMobilePromptResponse(e,t,n,i,r=!1,o,c,a,l){let d=o??i.promptGenerationToken,P=c,w=a;if(!r&&d){let f=d.promptId.length>0?d.promptId:`__prompt_gen_${d.gen}`;if(i.inFlightPromptIds?.has(f)){s.warn("[walker] Duplicate mobile USER_PROMPT for same prompt \u2014 dropping",{sessionId:e,eventId:t.eventId,lockKey:f}),await this.markEventExecutedIdempotent(i,t);return}i.inFlightPromptIds||(i.inFlightPromptIds=new Set),i.inFlightEventIds||(i.inFlightEventIds=new Set),i.inFlightPromptIds.add(f),i.inFlightEventIds.add(t.eventId),P=f,w=t.eventId}try{if(!r&&i.processedEventIds?.has(t.eventId)){s.info("[walker] Redelivered event already processed \u2014 skipping",{sessionId:e,eventId:t.eventId});return}let f=n.trim(),g=l?l.pendingPromptId:i.pendingPromptId,m=l?l.pendingSubmitMap:i.pendingSubmitMap,I=l?l.pendingPanePromptIdentity:i.pendingPanePromptIdentity,h=l?l.activeAskUserQuestionInvocation:i.activeAskUserQuestionInvocation,y=m,v=this.parseInteractivePromptInput(f,Object.keys(y??{}));if(s.info("Parsed interactive prompt input",{sessionId:e,content:f,parsed:v,hasSubmitMap:!!y}),v.action==="send_as_response"&&I?.kind==="ask_user_question"){let x=i.promptGenerationToken,F=this.activeSessions.get(e)===i&&x?.gen===d?.gen&&x?.promptId===d?.promptId,S=h,A=F&&S?{expectedIdentity:I,invocation:S,attempts:0,nextAttemptAt:Date.now()}:void 0,O=await this.enqueueMobilePrompt(e,{...t,content:n},A,i.claudeSessionId);if(!F||!S){this.scheduleQueuedMobilePromptDrain(e);return}let _=await this.dismissLivePromptForConversation(i,d);_&&O.pendingDismissal&&(O.pendingDismissal.dismissalSent=!0,await this.mobilePromptQueueStore.put(O.nativeSessionId,O));let Q=_&&S&&await this.resolveAskUserQuestionAfterVerifiedDismissal(i,I,S);Q&&O.pendingDismissal&&(O.pendingDismissal=void 0,await this.mobilePromptQueueStore.put(O.nativeSessionId,O)),this.scheduleQueuedMobilePromptDrain(e),Q||await this.sendPromptError(e,"The active prompt could not be dismissed yet; your message is safely queued");return}let b=d&&h?{token:{...d},invocation:h}:void 0,E=()=>{if(b&&this.isExpectedAskUserQuestionTransitionCurrent(e,i,b))return!1;let x=i.promptGenerationToken,F=x?.gen,S=d?.gen;return this.activeSessions.get(e)!==i||F!==S?(s.warn("[walker] Token mismatch \u2014 external cleanup or new prompt during in-flight handler \u2014 aborting",{sessionId:e,eventId:t.eventId,entryToken:d,currentToken:x}),!0):!1};if(E()){await this.markEventExecutedIdempotent(i,t);return}if(v.action==="select_option"){let x=y?.[v.option]||v.option,F=i.pendingQuestionsQueue!==void 0,S=i.pendingCurrentQuestion?.options?.length??0,A=v.option?parseInt(v.option,10):NaN,O=i.pendingSynthTail?.length??0;if(F&&i.pendingCurrentQuestion!==void 0&&O>0&&Number.isFinite(A)&&A>S&&A<=S+O){if(await this.markEventExecutedIdempotent(i,t),E())return;let T=await this.sendVerifiedPromptKey(i,I,"Escape");if(E())return;if(!(T&&I?.kind==="ask_user_question"&&h&&await this.resolveAskUserQuestionAfterVerifiedDismissal(i,I,h))){await this.sendPromptError(e,"The active prompt could not be dismissed; please retry");return}await this.emitUserChoice(e,"\u2192 AskUserQuestion dismissed; enter your reply as a new prompt");return}s.info("User selected option",{option:v.option,terminalInput:x,isV2AskUserQuestion:F});let Q=await this.answerVerifiedInteractivePrompt(i,I,x,{pressEnter:!F});if(E()){await this.markEventExecutedIdempotent(i,t);return}if(Q){if(await this.markEventExecutedIdempotent(i,t),E())return;if(!g){s.warn("emitAnswerAck called without promptId \u2014 clearing state + skipping ack",{sessionId:e,source:"select_option",eventId:t.eventId}),this.promoteFingerprintAndClearPromptState(i);return}let T=(i.pendingQuestionsQueue?.length??0)===0;try{if(F){let G=parseInt(v.option,10)-1,X=i.pendingCurrentQuestion?.options?.[G],ee=typeof X=="string"?X:X&&typeof X=="object"?X.label:`option ${v.option}`,Ue=i.pendingCurrentQuestion?._isSubmit===!0,Me=ee.toLowerCase(),ae;Ue&&Me==="cancel"?ae="\u2192 Cancel \u2014 AskUserQuestion cancelled, no answers submitted":Ue&&Me.startsWith("submit")?ae="\u2192 Submit answers \u2014 AskUserQuestion completed":ae=`\u2192 ${ee}`,await this.emitUserChoice(e,ae)}else await this.emitAnswerAck(e,`Selected option ${v.option}`,{promptId:g,questionIndex:0,isTerminal:T})}catch(G){s.warn("[walker] user-choice/ack emit failed \u2014 continuing to STEP 7/8",{sessionId:e,promptId:g,isV2AskUserQuestion:F,error:G instanceof Error?G.message:String(G)})}if(E())return;let k=i.pendingQuestionsQueue?.shift();if(k&&(i.pendingCurrentQuestion=k,i.pendingPanePromptIdentity=this.buildAskUserQuestionPaneIdentity(k,i.activeAskUserQuestionInvocation?.occurrenceId)),!k){i.activeAskUserQuestionFingerprint&&(i.completedAskUserQuestionFingerprints||(i.completedAskUserQuestionFingerprints=new Set),i.completedAskUserQuestionFingerprints.add(i.activeAskUserQuestionFingerprint),s.info("AskUserQuestion V2 walker complete \u2014 fingerprint marked closed",{sessionId:e,fingerprint:i.activeAskUserQuestionFingerprint.slice(0,16)})),this.clearPromptState(i);return}let H=this.newE1Raise(e,!0),U=H.promptId;if(i.activeAskUserQuestionInvocation?.e1PromptIds.push(U),!U){this.promoteFingerprintAndClearPromptState(i),s.warn("Q[next] emit aborted: synthesized promptId was empty; promoted fingerprint + cleared prompt state",{sessionId:e,eventId:t.eventId});return}let q=i.pendingSynthTail??[],z=this.buildQuestionWireData(k,q),J=k.question,Y={tool_name:"AskUserQuestion",tool_input:{questions:[k]},options:z.options,submitMap:z.submitMap,instructions:z.instructions},W=i.encryptionSnapshot,M=(0,p.encryptForEmit)(W?.sessionKey??null,W?.sessionIsEncrypted??!1,J,Y,W?W.encryptionResolved:!1);if(!M){s.error("No session key for ENCRYPTED session \u2014 dropping Q[next] prompt (fail-closed, #638)",{sessionId:e,promptId:U}),this.promoteFingerprintAndClearPromptState(i);return}let C=M.content,Z=M.metadata,yt=M.isEncrypted,vt=this.nextPromptGen++,be={promptId:U,gen:vt};i.pendingPromptId=U,i.pendingSubmitMap=z.submitMap,i.promptGenerationToken=be;let ie=be,oe=this.activeSessions.get(e)?.promptGenerationToken;if(!oe||oe.gen!==ie.gen||oe.promptId!==ie.promptId){s.warn("Q[next] emit aborted: token replaced before await dispatch",{sessionId:e,tokenAtAwait:ie,currentToken:oe});return}let xe=U.length>0?U:`__prompt_gen_${be.gen}`;i.inFlightPromptIds||(i.inFlightPromptIds=new Set),i.inFlightPromptIds.add(xe);try{this.stashE1RaiseContent(U,J,Y);try{await this.promptPublications.run(U,()=>this.appSyncClient.createEvent({sessionId:e,type:p.EventType.INTERACTIVE_PROMPT,source:p.EventSource.DESKTOP,content:C,metadata:Z,...(0,p.raiseFieldsFromRecord)(H),timestamp:(0,p.prepareEventTimestamp)({orderingKey:e}),isEncrypted:yt?!0:void 0})),s.info("Q[next] emit succeeded",{sessionId:e,promptId:U,remaining:i.pendingQuestionsQueue?.length??0})}catch(G){let X=this.activeSessions.get(e),ee=X?.promptGenerationToken;ee&&ee.gen===ie.gen&&ee.promptId===ie.promptId?(this.promoteFingerprintAndClearPromptState(X),s.warn("Q[next] emit failed; promoted fingerprint + cleared prompt state. User must answer remaining questions on desktop terminal.",{sessionId:e,promptId:U,error:G instanceof Error?G.message:String(G)})):s.warn("Q[next] emit failed but a NEW prompt replaced our token during await; not clearing state (would wipe new prompt). Q[next..QN] of the original AskUserQuestion are lost; new prompt continues normally.",{sessionId:e,tokenAtAwait:ie,currentToken:ee,error:G instanceof Error?G.message:String(G)})}}finally{i.inFlightPromptIds.delete(xe)}}else try{await this.sendPromptError(e,"Failed to select option")}catch(T){s.warn("[walker] sendPromptError threw \u2014 relying on idempotent mark in finally",{sessionId:e,eventId:t.eventId,error:String(T)})}finally{await this.markEventExecutedIdempotent(i,t)}}else if(v.action==="option_with_followup"){if(i.pendingQuestionsQueue!==void 0){let A=this.getWalkerPosition(i),O=i.pendingCurrentQuestion?.options?.length??0,_=v.option?parseInt(v.option,10):NaN,Q=Number.isFinite(_)&&_>O,T=v.followUpText??"";if(s.info("AUQ walker \u2014 option_with_followup dispatch",{sessionId:e,option:v.option,optionNum:_,explicitCount:O,isSynthDigit:Q,walkerPosition:A,followUpTextLen:T.length}),await this.markEventExecutedIdempotent(i,t),E())return;if(Q){if(A==="on_synth"){let M=await this.answerVerifiedInteractivePrompt(i,I,"2",{pressEnter:!1});if(E())return;if(!M){try{await this.emitUserChoice(e,"AskUserQuestion cancel keypress failed (tmux unavailable); your reply was not sent")}catch(C){s.warn("emitUserChoice on synth-digit on-SYNTH Cancel failed",{sessionId:e,error:C instanceof Error?C.message:String(C)})}return}if(await new Promise(C=>setTimeout(C,1500)),E())return;if(T){let C=await this.sendFreeFormWhenNoChooser(e,i,T,b);if(E())return;C||s.warn("on-SYNTH Cancel followup text-send failed",{sessionId:e})}try{await this.emitUserChoice(e,"\u2192 Cancel \u2014 AskUserQuestion cancelled, sending your reply as a new prompt")}catch(C){s.warn("emitUserChoice on synth-digit on-SYNTH success failed",{sessionId:e,error:C instanceof Error?C.message:String(C)})}if(E())return;this.promoteFingerprintAndClearPromptState(i);return}await this.handleMobileReplyAsDismissAndPrompt(e,i,I,T,b,E);return}let k=y?.[v.option]||v.option,H=_-1,U=i.pendingCurrentQuestion?.options?.[H],q=typeof U=="string"?U:U&&typeof U=="object"?U.label:`option ${v.option}`;if(A==="q1"){let M=await this.answerVerifiedInteractivePrompt(i,I,k,{pressEnter:!1});if(E())return;if(!M){try{await this.sendPromptError(e,"Failed to select option. Your reply (including the follow-up text) was not sent. Please retry.")}catch(C){s.warn("sendPromptError on q1 option_with_followup failed",{sessionId:e,error:C instanceof Error?C.message:String(C)})}return}if(await new Promise(C=>setTimeout(C,1500)),E())return;if(T){let C=await this.sendFreeFormWhenNoChooser(e,i,T,b);if(E())return;C||s.warn("q1 option_with_followup followup text-send failed",{sessionId:e})}try{await this.emitUserChoice(e,T?`\u2192 ${q} + sending your reply as a new prompt`:`\u2192 ${q}`)}catch(C){s.warn("emitUserChoice on q1 option_with_followup success failed",{sessionId:e,error:C instanceof Error?C.message:String(C)})}if(E())return;this.promoteFingerprintAndClearPromptState(i);return}if(A==="nq_mid"){let M=await this.answerVerifiedInteractivePrompt(i,I,k,{pressEnter:!1});if(E())return;if(!M){try{await this.sendPromptError(e,"Failed to commit Q[i]. Your reply was not sent. Please retry.")}catch(Z){s.warn("sendPromptError on nq_mid option_with_followup failed",{sessionId:e,error:Z instanceof Error?Z.message:String(Z)})}return}if(await new Promise(Z=>setTimeout(Z,200)),E())return;let C=i.pendingQuestionsQueue?.shift();C?(i.pendingCurrentQuestion=C,i.pendingPanePromptIdentity=this.buildAskUserQuestionPaneIdentity(C,i.activeAskUserQuestionInvocation?.occurrenceId)):s.warn("nq_mid option_with_followup: queue.shift returned undefined unexpectedly",{sessionId:e}),await this.handleMobileReplyAsDismissAndPrompt(e,i,i.pendingPanePromptIdentity,T,b,E);return}if(A==="nq_last"){await this.handleOptionWithFollowupOnLastQ(e,i,I,k,q,T,b,E);return}let z=await this.answerVerifiedInteractivePrompt(i,I,k,{pressEnter:!1});if(E())return;if(!z){try{await this.sendPromptError(e,"Failed to send SYNTH keypress. Your reply was not sent. Please retry.")}catch(M){s.warn("sendPromptError on on-SYNTH option_with_followup failed",{sessionId:e,error:M instanceof Error?M.message:String(M)})}return}if(await new Promise(M=>setTimeout(M,1500)),E())return;if(T){let M=await this.sendFreeFormWhenNoChooser(e,i,T,b);if(E())return;M||s.warn("on-SYNTH option_with_followup text-send failed",{sessionId:e})}let J=i.pendingCurrentQuestion?._isSubmit===!0,Y=q.toLowerCase(),W;J&&Y==="cancel"?W=T?"\u2192 Cancel \u2014 AskUserQuestion cancelled, sending your reply as a new prompt":"\u2192 Cancel \u2014 AskUserQuestion cancelled, no answers submitted":J&&Y.startsWith("submit")?W=T?"\u2192 Submit answers \u2014 AskUserQuestion completed, sending your reply as a new prompt":"\u2192 Submit answers \u2014 AskUserQuestion completed":W=T?`\u2192 ${q} + sending your reply as a new prompt`:`\u2192 ${q}`;try{await this.emitUserChoice(e,W)}catch(M){s.warn("emitUserChoice on on-SYNTH option_with_followup success failed",{sessionId:e,error:M instanceof Error?M.message:String(M)})}if(E())return;this.promoteFingerprintAndClearPromptState(i);return}let F=y?.[v.option]||v.option;s.info("User selected option with follow-up",{option:v.option,terminalInput:F,followUpText:v.followUpText});let S=await this.answerVerifiedInteractivePrompt(i,I,F);if(E()){await this.markEventExecutedIdempotent(i,t);return}if(S){if(await this.markEventExecutedIdempotent(i,t),E())return;if(!g){s.warn("emitAnswerAck called without promptId \u2014 clearing state + skipping ack",{sessionId:e,source:"option_with_followup",eventId:t.eventId}),this.promoteFingerprintAndClearPromptState(i);return}try{await this.emitAnswerAck(e,`Selected option ${v.option}`,{promptId:g,questionIndex:0,isTerminal:!0})}catch(A){s.warn("[walker] emitAnswerAck (option_with_followup) failed \u2014 continuing to clearPromptState + executeMobilePrompt",{sessionId:e,promptId:g,error:A instanceof Error?A.message:String(A)})}if(E())return;if(this.promoteFingerprintAndClearPromptState(i),v.followUpText){await new Promise(O=>setTimeout(O,1e3));let A={...t,content:v.followUpText};await this.executeMobilePrompt(e,A)}}else try{await this.sendPromptError(e,"Failed to select option. Your reply (including the follow-up text) was not sent. Please retry.")}catch(A){s.warn("[walker] sendPromptError threw \u2014 relying on idempotent mark in finally",{sessionId:e,eventId:t.eventId,error:String(A)})}finally{await this.markEventExecutedIdempotent(i,t)}}else{if(i.pendingQuestionsQueue!==void 0){let S=this.getWalkerPosition(i);if(s.info("AUQ walker \u2014 send_as_response dispatch",{sessionId:e,walkerPosition:S,contentPreview:f.slice(0,80)}),await this.markEventExecutedIdempotent(i,t),E())return;if(S==="on_synth"){let A=await this.answerVerifiedInteractivePrompt(i,I,"2",{pressEnter:!1});if(E())return;if(!A){try{await this.emitUserChoice(e,"AskUserQuestion cancel keypress failed (tmux unavailable); your reply was not sent")}catch(_){s.warn("emitUserChoice on send_as_response on-SYNTH Cancel failed",{sessionId:e,error:_ instanceof Error?_.message:String(_)})}return}if(await new Promise(_=>setTimeout(_,1500)),E())return;let O=await this.sendFreeFormWhenNoChooser(e,i,f,b);if(E())return;O||s.warn("send_as_response on-SYNTH text-send failed",{sessionId:e});try{await this.emitUserChoice(e,"\u2192 Cancel \u2014 AskUserQuestion cancelled, sending your reply as a new prompt")}catch(_){s.warn("emitUserChoice on send_as_response on-SYNTH success failed",{sessionId:e,error:_ instanceof Error?_.message:String(_)})}if(E())return;this.promoteFingerprintAndClearPromptState(i);return}await this.handleMobileReplyAsDismissAndPrompt(e,i,I,f,b,E);return}s.info("Rejecting legacy prompt before forwarding free-form mobile input",{responsePreview:f.slice(0,80),attachmentCount:t.attachments?.length??0});let F=await this.dismissLivePromptForConversation(i,d);if(E()){await this.markEventExecutedIdempotent(i,t);return}if(F)await this.clearInteractivePromptsForAcceptedInput(i),await new Promise(S=>setTimeout(S,200)),await this.executeMobilePrompt(e,{...t,content:f});else try{await this.sendPromptError(e,"Failed to dismiss the active prompt; your message was not sent")}catch(S){s.warn("[walker] sendPromptError threw \u2014 relying on idempotent mark in finally",{sessionId:e,eventId:t.eventId,error:String(S)})}finally{await this.markEventExecutedIdempotent(i,t)}}}finally{P&&i.inFlightPromptIds&&i.inFlightPromptIds.delete(P),w&&i.inFlightEventIds&&i.inFlightEventIds.delete(w)}}async sendInteractivePromptAsync(e,t,n){let i=this.activeSessions.get(e),r=i?.activeAskUserQuestionInvocation?.occurrenceId,o=i?.promptGenerationToken?{...i.promptGenerationToken}:void 0,c=(0,p.prepareEventTimestamp)({orderingKey:e});await new Promise(S=>setTimeout(S,500));let a=process.env.CODEVIBE_TMUX_SESSION,l={...t.metadata||{}},d,P=t.metadata?.tool_name,w=t.metadata?.tool_input,f=P==="AskUserQuestion"&&Array.isArray(w?.questions)?w.questions:[];if(f.length>0&&Array.isArray(f[0]?.options)&&f[0].options.length>0){let S=f[0];d=this.buildAskUserQuestionPaneIdentity(S,r);let A=[];if(a)try{let{exec:k}=await import("child_process"),H=q=>new Promise((z,J)=>{k(q,{timeout:5e3},(Y,W)=>{Y?J(Y):z({stdout:W||""})})}),{stdout:U}=await H(`tmux capture-pane -p -e -S -30 -t '${a}'`);A=this.parseAskUserQuestionSynthTail(U),s.info("AskUserQuestion synth-tail parsed from tmux",{tailCount:A.length,tail:A.map(q=>q.label)})}catch(k){s.warn("Failed to capture tmux for AskUserQuestion synth-tail; emitting without synth tail",{error:k instanceof Error?k.message:String(k)})}else s.info("No tmux session \u2014 AskUserQuestion synth tail will be empty");let O=this.activeSessions.get(e);if(O){let k=O.promptGenerationToken;o&&k?.gen===o.gen?O.pendingSynthTail=A:s.warn("AskUserQuestion synth-tail: stale async \u2014 token gen mismatch, skipping pendingSynthTail write",{tokenAtEmit:o,currentToken:k,sessionId:e})}let _=this.buildQuestionWireData(S,A);l.options=JSON.parse(JSON.stringify(_.options)),l.submitMap=JSON.parse(JSON.stringify(_.submitMap)),l.instructions=_.instructions,l.tool_name="AskUserQuestion",l.tool_input={questions:[S]},n=S.question;let Q=typeof t.prompt_id=="string"&&t.prompt_id.length>0,T=Yt(f);if(Q){let k=this.activeSessions.get(e);if(k){let H=k.promptGenerationToken;o&&H?.gen===o.gen?(k.pendingQuestionsQueue=T.remainingQueue,k.pendingCurrentQuestion=S,k.pendingPanePromptIdentity=d,k.hasReviewScreen=T.hasReviewScreen):s.warn("AskUserQuestion: stale async \u2014 token gen mismatch, skipping walker-state write",{tokenAtEmit:o,currentToken:H,sessionId:e})}}else s.warn("AskUserQuestion: empty prompt_id, degrading to single-Q legacy emit",{questionCount:f.length});s.info("AskUserQuestion: emitting Q1 only (Q2..QN queued)",{questionCount:T.questionCount,hasReviewScreen:T.hasReviewScreen,queuedRemaining:Q?T.remainingQueue.length:0,optionCountFirst:_.options.length,questionPreview:S.question.slice(0,80)})}else if(a)try{let{exec:S}=await import("child_process"),A=T=>new Promise((k,H)=>{S(T,{timeout:5e3},(U,q)=>{U?H(U):k({stdout:q||""})})}),{stdout:O}=await A(`tmux capture-pane -p -e -S -30 -t '${a}'`),_=O.split(`
|
|
7
|
+
`);s.info("tmux capture result",{tmuxSession:a,totalLines:_.length,lastLines:_.slice(-15).map(T=>T.replace(/\x1B[^m]*m/g,"").trim()).filter(Boolean)});let Q=(0,p.parseInteractivePrompt)(O);if(Q&&Q.options.length>0)l.options=Q.options,l.submitMap=Q.submitMap,l.instructions=this.buildPromptInstructions(Q),d=this.buildParsedPanePromptIdentity(Q),s.info("Parsed dynamic options from tmux",{optionCount:Q.options.length,kind:Q.kind,options:Q.options});else{this.suppressPromptOptionsUnavailable(e,o,"tmux parse returned no options");return}}catch(S){this.suppressPromptOptionsUnavailable(e,o,`tmux capture failed: ${S instanceof Error?S.message:String(S)}`);return}else{this.suppressPromptOptionsUnavailable(e,o,"no tmux session");return}let g=this.activeSessions.get(e),m=!o||g?.promptGenerationToken?.gen===o.gen;g&&l.submitMap&&m&&(g.pendingSubmitMap=l.submitMap,g.pendingPanePromptIdentity=d);let I=this.encryptForEmit(n,l,p.EventType.INTERACTIVE_PROMPT,e);if(!I)return;let h=I.content,y=I.metadata,v=I.isEncrypted,E=this.activeSessions.get(e)?.promptGenerationToken;if(o&&E?.gen!==o.gen){s.warn("Interactive prompt emit: stale token \u2014 newer INTERACTIVE_PROMPT replaced ours; skipping AppSync emit",{sessionId:e,tokenAtEmit:o,currentToken:E});return}let x=t.prompt_id?this.e1PromptRaises.get(t.prompt_id):void 0;if(t.prompt_id&&(this.e1NeedsResolution.has(t.prompt_id)||o&&!x))return;let F=x?(0,p.raiseFieldsFromRecord)(x.record):{};x&&this.stashE1RaiseContent(x.record.promptId,n,l),await this.appSyncClient.createEvent({sessionId:e,type:p.EventType.INTERACTIVE_PROMPT,source:t.source,content:h,metadata:y,promptId:t.prompt_id,...F,timestamp:c,isEncrypted:v?!0:void 0}),s.info("Interactive prompt sent to AppSync with dynamic options",{sessionId:e})}buildQuestionWireData(e,t=[]){let n=(e.options||[]).map((a,l)=>{let d=typeof a=="string",P=d?a:a.label||"",w=d?"":a.description||"",f=d?"":a.preview||"",g={number:String(l+1),text:P};return w&&(g.description=w),f&&(g.preview=f),g});if(!e._isSubmit)for(let a of t)n.push({number:String(n.length+1),text:a.label});let i=Object.fromEntries(n.map(a=>[a.number,a.number])),r=(e.options||[]).length,o=t.findIndex(a=>a.label==="Type something"),c;if(e._isSubmit)c="Reply with 1 to submit your answers or 2 to cancel.";else if(e.multiSelect)c=`Reply with comma-separated numbers (e.g., 1,3) for "${e.header||e.question}"`;else if(o>=0){let a=String(r+o+1);c=`Reply with the number of your choice. For option ${a} (Type something), reply "${a}, your answer".`}else c="Reply with the number of your choice.";return{options:n,submitMap:i,instructions:c}}parseAskUserQuestionSynthTail(e){let t=(0,p.normalizeSnapshot)(e);if(!t)return[];let n=t.split(`
|
|
8
|
+
`).slice(-14),i=[],r=new Set,o=/^\s*(?:[›❯▸▶➜➤]\s*)?(?:\d+\.\s+)?(Type something|Chat about this)\.?\s*$/i;for(let c of n){let a=c.match(o);if(!a)continue;let d=a[1].toLowerCase()==="type something"?"Type something":"Chat about this";r.has(d)||(i.push({label:d}),r.add(d))}return i}suppressPromptOptionsUnavailable(e,t,n){s.warn("Interactive prompt: real options unavailable \u2014 suppressing mobile prompt (no fabricated options)",{sessionId:e,reason:n});let i=t?.promptId,r=i?this.e1PromptRaises.get(i):void 0;r&&(r.record.mobileActionable=!1,this.emitSuppressedPromptCarrier(e,r.record).catch(c=>{s.error("E1: failed to emit suppressed-prompt badge carrier",{sessionId:e,promptId:r.record.promptId,error:c instanceof Error?c.message:String(c)})}));let o=this.activeSessions.get(e);o&&t&&o.promptGenerationToken?.gen===t.gen&&this.clearPromptState(o)}async emitSuppressedPromptCarrier(e,t){let n=this.e1PromptRaises.get(t.promptId);if(!n||n.record!==t||this.e1NeedsResolution.has(t.promptId))return!1;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.",r=this.encryptForEmit(i,{e1SuppressedPrompt:!0},p.EventType.NOTIFICATION,e);return r?(await this.promptPublications.run(t.promptId,()=>this.appSyncClient.createEvent({sessionId:e,type:p.EventType.NOTIFICATION,source:p.EventSource.DESKTOP,content:r.content,metadata:r.metadata,...(0,p.raiseFieldsFromRecord)(t),notificationText:i,timestamp:(0,p.prepareEventTimestamp)({orderingKey:e}),isEncrypted:r.isEncrypted?!0:void 0})),s.info("E1: emitted suppressed-prompt badge carrier (mobileActionable=false)",{sessionId:e,promptId:t.promptId}),!0):!1}buildPromptInstructions(e){return`Reply with ${e.options.map(n=>n.number).join(", ")}. Append a message to provide alternative instructions.`}parseInteractivePromptInput(e,t){return ft(e,t)}async markEventExecuted(e){try{await this.appSyncClient.updateEventStatus({eventId:e.eventId,sessionId:e.sessionId,timestamp:e.timestamp,deliveryStatus:p.DeliveryStatus.EXECUTED}),s.info("Event marked as EXECUTED",{eventId:e.eventId})}catch(t){s.warn("Failed to mark event as EXECUTED",{eventId:e.eventId,error:t})}}async sendPromptError(e,t){let n={error:!0},i=this.encryptForEmit(t,n,p.EventType.NOTIFICATION,e);if(!i)return;let r=i.content,o=i.metadata,c=i.isEncrypted;await this.appSyncClient.createEvent({sessionId:e,type:p.EventType.NOTIFICATION,source:p.EventSource.DESKTOP,content:r,metadata:o,timestamp:(0,p.prepareEventTimestamp)({orderingKey:e}),isEncrypted:c?!0:void 0})}async emitUserChoice(e,t){let n=this.encryptForEmit(t,{source:"codevibe_v2_user_choice"},p.EventType.ASSISTANT_RESPONSE,e);if(!n)return;let i=n.content,r=n.metadata,o=n.isEncrypted;await this.appSyncClient.createEvent({sessionId:e,type:p.EventType.ASSISTANT_RESPONSE,source:p.EventSource.DESKTOP,content:i,metadata:r,timestamp:(0,p.prepareEventTimestamp)({orderingKey:e}),isEncrypted:o?!0:void 0})}async emitAnswerAck(e,t,n){let i={promptAnswered:!0,...n},r=this.encryptForEmit(t,i,p.EventType.NOTIFICATION,e);if(!r)return;let o=r.content,c=r.metadata,a=r.isEncrypted;await this.appSyncClient.createEvent({sessionId:e,type:p.EventType.NOTIFICATION,source:p.EventSource.DESKTOP,content:o,metadata:c,timestamp:(0,p.prepareEventTimestamp)({orderingKey:e}),isEncrypted:a?!0:void 0})}promoteFingerprintAndClearPromptState(e){e.activeAskUserQuestionFingerprint&&(e.completedAskUserQuestionFingerprints||(e.completedAskUserQuestionFingerprints=new Set),e.completedAskUserQuestionFingerprints.add(e.activeAskUserQuestionFingerprint)),this.clearPromptState(e)}normalizePromptIdentityText(e){return(0,p.normalizeSnapshot)(e).replace(/\s+/g," ").trim().toLowerCase()}buildParsedPanePromptIdentity(e){let t=[e.kind,this.normalizePromptIdentityText(e.promptText),...e.options.map(n=>`${n.number}:${this.normalizePromptIdentityText(n.text)}`)].join("::");return{kind:"parsed",key:(0,te.createHash)("sha256").update(t).digest("hex")}}buildAskUserQuestionPaneIdentity(e,t){let n=this.normalizePromptIdentityText(e.question??""),i=(e.options??[]).map(o=>this.normalizePromptIdentityText(typeof o=="string"?o:[o.label,o.description,o.preview].filter(Boolean).join(" "))),r=["ask_user_question",n,...i].join("::");return{kind:"ask_user_question",key:(0,te.createHash)("sha256").update(r).digest("hex"),questionText:n,optionLabels:i,hookOccurrenceId:t}}async captureLivePromptPane(){let e=process.env.CODEVIBE_TMUX_SESSION;if(!e)throw new Error("CODEVIBE_TMUX_SESSION is unset");let t=e.replace(/'/g,"'\\''"),{stdout:n}=await we(`tmux capture-pane -p -e -S -40 -t '${t}'`,{timeout:3e3});return n||""}liveAskUserQuestionMatches(e,t){let n=(0,p.normalizeSnapshot)(e).split(`
|
|
9
9
|
`).map(m=>m.trim()).filter(Boolean);if(n.length===0||!t.questionText||!t.optionLabels)return!1;let i=/(?:Esc to cancel|Enter to select|↑\/↓ to navigate|Tab to switch|shift\+tab|Chat about this|Type something|Notes: press|\(esc\)|\(shift\+tab\))/i,r=n.length-1,o=-1;for(let m=r;m>=Math.max(0,r-3);m-=1)if(i.test(n[m])){o=m;break}if(o<0)return!1;let c=/^(?:[›❯▸▶➜➤]\s*)?(\d+)\.\s+(.+)$/,a=[];for(let m=Math.max(0,o-30);m<o;m+=1){let I=n[m].match(c);I&&a.push({index:m,number:Number(I[1]),firstLine:I[2]})}let l=[...a].reverse().find(m=>m.number===1);if(!l)return!1;let d=a.filter(m=>m.index>=l.index);if(d.length!==t.optionLabels.length||d.some((m,I)=>m.number!==I+1)||d.map((m,I)=>{let h=d[I+1]?.index??o,y=[m.firstLine,...n.slice(m.index+1,h).filter(v=>!i.test(v)&&!/^[─━-]+$/.test(v))];return this.normalizePromptIdentityText(y.join(" "))}).some((m,I)=>m!==t.optionLabels[I]))return!1;let w=d[0].index,f=Math.max(0,w-14);for(let m=w-1;m>=f;m-=1)if(i.test(n[m])){f=m+1;break}let g=this.normalizePromptIdentityText(n.slice(f,w).join(" "));return g===t.questionText||g.endsWith(` ${t.questionText}`)}livePromptHasNegativeAffordance(e){return(0,p.normalizeSnapshot)(e).split(`
|
|
10
10
|
`).map(n=>n.trim()).filter(Boolean).slice(-3).some(n=>/(?:Esc to cancel|\(esc\))/i.test(n))}liveParsedPromptIsActive(e,t){let n=(0,p.normalizeSnapshot)(e).split(`
|
|
11
11
|
`).map(a=>a.trim()).filter(Boolean);if(n.length===0)return!1;let i=n.length-1;if(t.kind==="yes_no"){let a=-1;for(let l=i;l>=Math.max(0,i-2);l-=1)if(/\[(?:y\/n|Y\/n|y\/N)\]/.test(n[l])){a=l;break}return a>=0}let r=/^(?:[›❯▸▶➜➤]\s*)?\d+\.\s+.+$/,o=-1;for(let a=i;a>=Math.max(0,i-6);a-=1)if(r.test(n[a])){o=a;break}if(o<0)return!1;let c=/(?:Esc to cancel|Enter to select|↑\/↓ to navigate|Tab to switch|shift\+tab|\(esc\)|\(shift\+tab\))/i;for(let a=i;a>=Math.max(o,i-1);a-=1)if(c.test(n[a]))return!0;return!1}async verifyLivePromptIdentity(e,t,n={}){if(!t)return!1;if(t.kind==="ask_user_question"){let i=e.promptGenerationToken,r=this.isPromptIdentityCurrent(e,t);return r||s.warn("Hook-owned AskUserQuestion identity is no longer current",{sessionId:e.sessionId,waitingForPromptResponse:e.waitingForPromptResponse,sameIdentityObject:e.pendingPanePromptIdentity===t,pendingPromptId:e.pendingPromptId,tokenPromptId:i?.promptId,tokenGeneration:i?.gen,sessionObjectCurrent:this.activeSessions.get(e.sessionId)===e}),r}if(!t||!this.isPromptIdentityCurrent(e,t))return!1;try{let i=await this.captureLivePromptPane();return this.isPromptIdentityCurrent(e,t)?this.livePromptMatchesIdentity(i,t,n):!1}catch(i){return s.warn("Unable to verify live Claude prompt before terminal input",{sessionId:e.sessionId,promptId:e.pendingPromptId,error:i instanceof Error?i.message:String(i)}),!1}}isPromptIdentityCurrent(e,t){return!t||e.pendingPanePromptIdentity!==t||typeof e.pendingPromptId!="string"||!e.promptGenerationToken||e.promptGenerationToken.promptId!==e.pendingPromptId?!1:t.kind!=="ask_user_question"?!0:e.waitingForPromptResponse===!0&&this.activeSessions.get(e.sessionId)===e&&typeof t.hookOccurrenceId=="string"&&e.activeAskUserQuestionInvocation?.occurrenceId===t.hookOccurrenceId}clearPromptStateIfCurrent(e,t){return!t||e.pendingPanePromptIdentity!==t?!1:(this.promoteFingerprintAndClearPromptState(e),!0)}livePromptMatchesIdentity(e,t,n={}){if(n.requireNegativeAffordance&&!this.livePromptHasNegativeAffordance(e))return!1;if(t.kind==="parsed"){let i=(0,p.parseInteractivePrompt)(e);return!!i&&this.liveParsedPromptIsActive(e,i)&&this.buildParsedPanePromptIdentity(i).key===t.key}return this.liveAskUserQuestionMatches(e,t)}async answerVerifiedInteractivePrompt(e,t,n,i){if(!await this.verifyLivePromptIdentity(e,t))return s.warn("Refusing mobile answer because the live Claude prompt changed or is uninspectable",{sessionId:e.sessionId,promptId:e.pendingPromptId}),!1;if(!this.isPromptIdentityCurrent(e,t))return s.warn("Refusing mobile answer because prompt identity changed before tmux input",{sessionId:e.sessionId,promptId:e.pendingPromptId}),!1;let r=t?.kind==="ask_user_question"?!1:i?.pressEnter??!0,o=await this.promptResponder.answerInteractivePrompt(e.sessionId,n,{pressEnter:!1});if(!o||!r)return o;if(await new Promise(c=>setTimeout(c,500)),!t||!this.isPromptIdentityCurrent(e,t))return s.warn("Refusing Enter because the pending Claude prompt changed after text input",{sessionId:e.sessionId,promptId:e.pendingPromptId}),!1;try{let c=await this.captureLivePromptPane();return this.isPromptIdentityCurrent(e,t)?this.livePromptMatchesIdentity(c,t)?this.promptResponder.sendKey(e.sessionId,"Enter"):this.liveAnyPromptIsActive(c)?(s.warn("Refusing Enter because a different Claude prompt appeared after text input",{sessionId:e.sessionId,promptId:e.pendingPromptId}),!1):(s.info("Claude committed the mobile option without a separate Enter key",{sessionId:e.sessionId,promptId:e.pendingPromptId}),!0):!1}catch(c){return s.warn("Unable to inspect the live Claude prompt after text input",{sessionId:e.sessionId,promptId:e.pendingPromptId,error:c instanceof Error?c.message:String(c)}),!1}}async sendVerifiedPromptKey(e,t,n){return await this.verifyLivePromptIdentity(e,t,{requireNegativeAffordance:n==="Escape"})?this.isPromptIdentityCurrent(e,t)?this.promptResponder.sendKey(e.sessionId,n):(s.warn("Refusing mobile key because prompt identity changed before tmux input",{sessionId:e.sessionId,promptId:e.pendingPromptId}),!1):(s.warn("Refusing mobile key because the live Claude prompt changed or is uninspectable",{sessionId:e.sessionId,promptId:e.pendingPromptId}),!1)}async dismissLivePromptForConversation(e,t){let n=()=>{let i=e.promptGenerationToken;return this.activeSessions.get(e.sessionId)===e&&i?.promptId===t?.promptId&&i?.gen===t?.gen};if(!n())return!1;try{let i=await this.captureLivePromptPane();return n()?this.liveAnyPromptIsActive(i)?!await this.promptResponder.sendKey(e.sessionId,"Escape")||!n()?!1:this.waitForChooserAbsence(e.sessionId,e):!0:!1}catch(i){return s.warn("Unable to dismiss the live Claude chooser before conversation continuation",{sessionId:e.sessionId,error:i instanceof Error?i.message:String(i)}),!1}}async resolveAskUserQuestionAfterVerifiedDismissal(e,t,n,i={}){let r=e.sessionId;if(t.kind!=="ask_user_question"||t.hookOccurrenceId!==n.occurrenceId)return!1;let o=await this.waitForChooserAbsence(r,e,i),c=e.activeAskUserQuestionInvocation?.occurrenceId;return c&&c!==n.occurrenceId?this.settleDismissedAskUserQuestionOccurrence(e,n):o?this.activeSessions.get(r)!==e||e.pendingPanePromptIdentity!==t||e.activeAskUserQuestionInvocation?.occurrenceId!==n.occurrenceId?(s.warn("Ignoring verified AskUserQuestion dismissal because the active occurrence changed",{sessionId:r,dismissedOccurrenceId:n.occurrenceId,activeOccurrenceId:e.activeAskUserQuestionInvocation?.occurrenceId}),!1):this.settleDismissedAskUserQuestionOccurrence(e,n,t):!1}async settleDismissedAskUserQuestionOccurrence(e,t,n){let i=e.sessionId,r=e.activeAskUserQuestionInvocation?.occurrenceId;if(this.activeSessions.get(i)!==e||r===t.occurrenceId&&(!n||e.pendingPanePromptIdentity!==n))return!1;let o=e.pendingAskUserQuestionInvocations,c=o?.findIndex(a=>a.occurrenceId===t.occurrenceId)??-1;o&&c>=0&&o.splice(c,1),e.completedAskUserQuestionFingerprints||(e.completedAskUserQuestionFingerprints=new Set),e.completedAskUserQuestionFingerprints.add(t.fingerprint),r===t.occurrenceId&&(this.clearPromptState(e),e.activeAskUserQuestionInvocation=void 0);for(let a of t.e1PromptIds)await this.resolveE1PluginApproval(i,a);return this.scheduleQueuedMobilePromptDrain(i),s.info("AskUserQuestion occurrence settled after verified dismissal",{sessionId:i,occurrenceId:t.occurrenceId,promptIds:t.e1PromptIds}),!0}liveAnyPromptIsActive(e){let t=(0,p.parseInteractivePrompt)(e);if(t&&this.liveParsedPromptIsActive(e,t))return!0;let n=(0,p.normalizeSnapshot)(e).split(`
|
|
12
|
-
`).map(r=>r.trim()).filter(Boolean),i=-1;for(let r=n.length-1;r>=0;r-=1)if(/(?:Esc to cancel|Enter to select|↑\/↓ to navigate|Tab to switch|\(esc\)|\(shift\+tab\))/i.test(n[r])){i=r;break}return i<Math.max(0,n.length-3)?!1:n.slice(Math.max(0,i-24),i).some(r=>/^(?:[›❯▸▶➜➤]\s*)?\d+\.\s+.+$/.test(r))}async waitForChooserAbsence(e,t,n={}){let i=Math.max(1,n.attempts??9),r=Math.max(0,n.delayMs??500);for(let o=0;o<i;o+=1){if(this.activeSessions.get(e)!==t)return!1;try{if(!this.liveAnyPromptIsActive(await this.captureLivePromptPane()))return this.activeSessions.get(e)===t}catch(c){s.warn("Unable to prove the Claude chooser is absent before free-form input",{sessionId:e,attempt:o+1,error:c instanceof Error?c.message:String(c)})}o+1<i&&await new Promise(c=>setTimeout(c,r))}return!1}async sendFreeFormWhenNoChooser(e,t,n,i){let r=t.promptStateEpoch??0,o=()=>i?this.isExpectedAskUserQuestionTransitionCurrent(e,t,i):this.activeSessions.get(e)===t&&(t.promptStateEpoch??0)===r&&!t.waitingForPromptResponse&&t.promptGenerationToken===void 0&&t.activeAskUserQuestionInvocation===void 0,c=async()=>{try{return!this.liveAnyPromptIsActive(await this.captureLivePromptPane())}catch(l){return s.warn("Unable to prove the Claude chooser stayed absent after free-form input",{sessionId:e,error:l instanceof Error?l.message:String(l)}),!1}};return!o()||!await this.waitForChooserAbsence(e,t)||!o()||!await this.promptResponder.answerInteractivePrompt(e,n,{pressEnter:!1})||(await new Promise(l=>setTimeout(l,500)),!o()||!await c()||!o())?!1:this.promptResponder.sendKey(e,"Enter")}isExpectedAskUserQuestionTransitionCurrent(e,t,n){if(this.activeSessions.get(e)!==t)return!1;let i=t.promptGenerationToken,r=t.activeAskUserQuestionInvocation?.occurrenceId;if(i?.promptId===n.token.promptId&&i.gen===n.token.gen&&r===n.invocation.occurrenceId)return!0;let o=t.pendingAskUserQuestionInvocations?.some(c=>c.occurrenceId===n.invocation.occurrenceId)??!1;return i===void 0&&!t.waitingForPromptResponse&&r===void 0&&!o&&typeof n.invocation.toolUseId=="string"&&n.invocation.toolUseId.length>0&&typeof n.invocation.completionEpoch=="number"&&t.promptStateEpoch===n.invocation.completionEpoch}clearPromptState(e){e.promptStateEpoch=(e.promptStateEpoch??0)+1,e.waitingForPromptResponse=!1,e.pendingPromptId=void 0,e.pendingSubmitMap=void 0,e.pendingPanePromptIdentity=void 0,e.pendingQuestionsQueue=void 0,e.pendingCurrentQuestion=void 0,e.pendingSynthTail=void 0,e.hasReviewScreen=void 0,e.activeAskUserQuestionFingerprint=void 0,e.promptGenerationToken=void 0,this.scheduleQueuedMobilePromptDrain(e.sessionId)}async detectWalkerActive(e){let t=process.env.CODEVIBE_TMUX_SESSION;if(!t)return s.debug("detectWalkerActive: no CODEVIBE_TMUX_SESSION env \u2014 conservatively assuming walker active",{sessionId:e}),!0;try{let{exec:n}=await import("child_process"),i=y=>new Promise((v,
|
|
12
|
+
`).map(r=>r.trim()).filter(Boolean),i=-1;for(let r=n.length-1;r>=0;r-=1)if(/(?:Esc to cancel|Enter to select|↑\/↓ to navigate|Tab to switch|\(esc\)|\(shift\+tab\))/i.test(n[r])){i=r;break}return i<Math.max(0,n.length-3)?!1:n.slice(Math.max(0,i-24),i).some(r=>/^(?:[›❯▸▶➜➤]\s*)?\d+\.\s+.+$/.test(r))}async waitForChooserAbsence(e,t,n={}){let i=Math.max(1,n.attempts??9),r=Math.max(0,n.delayMs??500);for(let o=0;o<i;o+=1){if(this.activeSessions.get(e)!==t)return!1;try{if(!this.liveAnyPromptIsActive(await this.captureLivePromptPane()))return this.activeSessions.get(e)===t}catch(c){s.warn("Unable to prove the Claude chooser is absent before free-form input",{sessionId:e,attempt:o+1,error:c instanceof Error?c.message:String(c)})}o+1<i&&await new Promise(c=>setTimeout(c,r))}return!1}async sendFreeFormWhenNoChooser(e,t,n,i){let r=t.promptStateEpoch??0,o=()=>i?this.isExpectedAskUserQuestionTransitionCurrent(e,t,i):this.activeSessions.get(e)===t&&(t.promptStateEpoch??0)===r&&!t.waitingForPromptResponse&&t.promptGenerationToken===void 0&&t.activeAskUserQuestionInvocation===void 0,c=async()=>{try{return!this.liveAnyPromptIsActive(await this.captureLivePromptPane())}catch(l){return s.warn("Unable to prove the Claude chooser stayed absent after free-form input",{sessionId:e,error:l instanceof Error?l.message:String(l)}),!1}};return!o()||!await this.waitForChooserAbsence(e,t)||!o()||!await this.promptResponder.answerInteractivePrompt(e,n,{pressEnter:!1})||(await new Promise(l=>setTimeout(l,500)),!o()||!await c()||!o())?!1:this.promptResponder.sendKey(e,"Enter")}isExpectedAskUserQuestionTransitionCurrent(e,t,n){if(this.activeSessions.get(e)!==t)return!1;let i=t.promptGenerationToken,r=t.activeAskUserQuestionInvocation?.occurrenceId;if(i?.promptId===n.token.promptId&&i.gen===n.token.gen&&r===n.invocation.occurrenceId)return!0;let o=t.pendingAskUserQuestionInvocations?.some(c=>c.occurrenceId===n.invocation.occurrenceId)??!1;return i===void 0&&!t.waitingForPromptResponse&&r===void 0&&!o&&typeof n.invocation.toolUseId=="string"&&n.invocation.toolUseId.length>0&&typeof n.invocation.completionEpoch=="number"&&t.promptStateEpoch===n.invocation.completionEpoch}clearPromptState(e){e.promptStateEpoch=(e.promptStateEpoch??0)+1,e.waitingForPromptResponse=!1,e.pendingPromptId=void 0,e.pendingSubmitMap=void 0,e.pendingPanePromptIdentity=void 0,e.pendingQuestionsQueue=void 0,e.pendingCurrentQuestion=void 0,e.pendingSynthTail=void 0,e.hasReviewScreen=void 0,e.activeAskUserQuestionFingerprint=void 0,e.promptGenerationToken=void 0,this.scheduleQueuedMobilePromptDrain(e.sessionId)}async detectWalkerActive(e){let t=process.env.CODEVIBE_TMUX_SESSION;if(!t)return s.debug("detectWalkerActive: no CODEVIBE_TMUX_SESSION env \u2014 conservatively assuming walker active",{sessionId:e}),!0;try{let{exec:n}=await import("child_process"),i=y=>new Promise((v,b)=>{n(y,{timeout:3e3},(E,x)=>{E?b(E):v({stdout:x||""})})}),{stdout:r}=await i(`tmux capture-pane -p -e -S -30 -t '${t}'`),o=/\x1B(?:\[[?!0-9;]*[A-Za-z]|\][^\x07\x1B]*(?:\x07|\x1B\\)|[()][A-B012])/g,c=r.split(`
|
|
13
13
|
`).map(y=>y.replace(o,"").trimEnd()),a=c.length-1;for(;a>=0&&c[a].trim()==="";)a-=1;if(a<0)return s.warn("detectWalkerActive: pane empty after ANSI strip \u2014 conservatively assuming walker active",{sessionId:e}),!0;let l=/(?:Esc to cancel|Enter to select|↑\/↓ to navigate|Tab to switch|shift\+tab|Chat about this|Type something|Notes: press|\(esc\)|\(shift\+tab\))/i,d=/^\s*(?:[›❯▸▶➜➤]\s*)?\d+\.\s+/,P=-1;for(let y=a;y>=0;y-=1)if(d.test(c[y])){P=y;break}let w=P>=0,f=-1;for(let y=a;y>=0;y-=1)if(l.test(c[y])){f=y;break}let g=f<0?1/0:a-f,m=f>=0&&g<=3,I=f>=0&&P>=0&&f>=P&&f-P<=5,h=w&&m&&I;return s.info("detectWalkerActive",{sessionId:e,walkerActive:h,hasParserBlock:w,chromeNearBottom:m,chromeFollowsBlock:I,chromeDistanceFromBottom:Number.isFinite(g)?g:-1,parserBlockEndsAt:P,lastNonBlank:a,lastChromeLineAt:f,lastLineSample:c[a]?.slice(0,80)??""}),h}catch(n){return s.warn("detectWalkerActive: tmux capture/parse failed \u2014 conservatively assuming walker active",{sessionId:e,error:n instanceof Error?n.message:String(n)}),!0}}getWalkerPosition(e){return e.pendingCurrentQuestion?._isSubmit?"on_synth":e.hasReviewScreen?(e.pendingQuestionsQueue?.length??0)>=2?"nq_mid":"nq_last":"q1"}async emitDegradedAUQNotification(e,t){try{let n=this.encryptForEmit(t,{source:"codevibe_auq_degraded"},p.EventType.ASSISTANT_RESPONSE,e);if(!n)return;let i=n.content,r=n.metadata,o=n.isEncrypted;await this.appSyncClient.createEvent({sessionId:e,type:p.EventType.ASSISTANT_RESPONSE,source:p.EventSource.DESKTOP,content:i,metadata:r,timestamp:(0,p.prepareEventTimestamp)({orderingKey:e}),isEncrypted:o?!0:void 0}),s.info("Emitted degraded-AUQ notification",{sessionId:e,textPreview:t.slice(0,60)})}catch(n){s.warn("emitDegradedAUQNotification failed",{sessionId:e,error:n instanceof Error?n.message:String(n)})}}async handleMobileReplyAsDismissAndPrompt(e,t,n,i,r,o){let c=await this.sendVerifiedPromptKey(t,n,"Escape");if(o())return!1;if(!c){try{await this.emitUserChoice(e,"AskUserQuestion dismissal failed (tmux unavailable)")}catch(l){s.warn("emitUserChoice on dismiss-ok1-failed failed",{sessionId:e,error:l instanceof Error?l.message:String(l)})}return!1}if(await new Promise(l=>setTimeout(l,200)),o())return!1;let a=await this.sendFreeFormWhenNoChooser(e,t,i,r);if(o())return!1;if(!a){try{await this.emitUserChoice(e,"AskUserQuestion dismissed but text-send failed; please retry")}catch(l){s.warn("emitUserChoice on dismiss-ok2-failed failed",{sessionId:e,error:l instanceof Error?l.message:String(l)})}return o()||this.promoteFingerprintAndClearPromptState(t),!1}try{await this.emitUserChoice(e,"\u2192 AskUserQuestion dismissed, sending your reply as a new prompt")}catch(l){s.warn("emitUserChoice on dismiss-success failed \u2014 continuing to promote+clear",{sessionId:e,error:l instanceof Error?l.message:String(l)})}return o()?(s.warn("handleMobileReplyAsDismissAndPrompt: aborted post-emitUserChoice (token rotated); leaving session state for new prompt",{sessionId:e}),!1):(this.promoteFingerprintAndClearPromptState(t),!0)}async handleOptionWithFollowupOnLastQ(e,t,n,i,r,o,c,a){let l=await this.answerVerifiedInteractivePrompt(t,n,i,{pressEnter:!1});if(a())return!1;if(!l){try{await this.emitUserChoice(e,"Failed to commit Q[N] (tmux unavailable); your reply was not sent")}catch(f){s.warn("emitUserChoice on lastQ-ok1-failed failed",{sessionId:e,error:f instanceof Error?f.message:String(f)})}return!1}if(await new Promise(f=>setTimeout(f,200)),a())return!1;let d=t.pendingQuestionsQueue?.[0];if(!d?._isSubmit)return s.warn("Last-Q flow reached review screen without a bound SYNTH identity",{sessionId:e}),!1;t.pendingCurrentQuestion=d,t.pendingPanePromptIdentity=this.buildAskUserQuestionPaneIdentity(d,t.activeAskUserQuestionInvocation?.occurrenceId);let P=await this.answerVerifiedInteractivePrompt(t,t.pendingPanePromptIdentity,"1",{pressEnter:!1});if(a())return!1;if(!P){try{await this.emitUserChoice(e,"Q[N] committed but Submit failed (tmux unavailable); please retry on desktop")}catch(f){s.warn("emitUserChoice on lastQ-ok2-failed failed",{sessionId:e,error:f instanceof Error?f.message:String(f)})}return a()||this.promoteFingerprintAndClearPromptState(t),!1}if(await new Promise(f=>setTimeout(f,1500)),a())return!1;let w=await this.sendFreeFormWhenNoChooser(e,t,o,c);if(a())return!1;w||s.warn("handleOptionWithFollowupOnLastQ: followup text send failed",{sessionId:e});try{await this.emitUserChoice(e,`\u2192 ${r} + sending your reply as a new prompt`)}catch(f){s.warn("emitUserChoice on lastQ-success failed \u2014 continuing to promote+clear",{sessionId:e,error:f instanceof Error?f.message:String(f)})}return a()?(s.warn("handleOptionWithFollowupOnLastQ: aborted post-emitUserChoice (token rotated); leaving session state for new prompt",{sessionId:e}),!1):(this.promoteFingerprintAndClearPromptState(t),!0)}registerAskUserQuestionInvocation(e,t,n,i){let r={occurrenceId:(0,te.randomUUID)(),fingerprint:t,generation:n,e1PromptIds:[i]};return e.pendingAskUserQuestionInvocations||(e.pendingAskUserQuestionInvocations=[]),e.pendingAskUserQuestionInvocations.push(r),r}computeAskUserQuestionFingerprint(e){if(!(!e||typeof e!="object"))try{let t=this.stringifyCanonical(e);return(0,te.createHash)("sha256").update(t).digest("hex")}catch(t){s.warn("Failed to fingerprint AskUserQuestion questions",{error:t instanceof Error?t.message:String(t)});return}}stringifyCanonical(e){return e===null||typeof e!="object"?JSON.stringify(e):Array.isArray(e)?"["+e.map(n=>this.stringifyCanonical(n)).join(",")+"]":"{"+Object.keys(e).sort().map(n=>JSON.stringify(n)+":"+this.stringifyCanonical(e[n])).join(",")+"}"}async markEventExecutedIdempotent(e,t){e.processedEventIds||(e.processedEventIds=new Set),e.processedEventIds.add(t.eventId);try{await this.markEventExecuted(t)}catch(n){s.warn("[walker] markEventExecuted threw \u2014 relying on processedEventIds set for dedup",{sessionId:t.sessionId,eventId:t.eventId,error:String(n)})}}subscribeToOrchestrationPrompts(e){return this.openInlineSubscription({kind:"orchestration-prompt",sessionId:e,query:`subscription OnEventCreated($sessionId: ID!) {
|
|
14
14
|
onEventCreated(sessionId: $sessionId) {
|
|
15
15
|
eventId
|
|
@@ -22,7 +22,7 @@ ${ct(u,"stdout")}`;return/^can't find session:/m.test(e)||/^no server running on
|
|
|
22
22
|
promptId
|
|
23
23
|
isEncrypted
|
|
24
24
|
}
|
|
25
|
-
}`,onPayload:async t=>{let n=t?.onEventCreated;if(!n||n.type!==p.EventType.INTERACTIVE_PROMPT)return;let i=this.activeSessions.get(e);if(!i)return;let r=n.metadata??void 0;if(n.isEncrypted&&r&&this.sessionKey)try{let P=JSON.parse(r);if(P&&typeof P=="object"&&typeof P.encrypted=="string"){let w=p.cryptoService.decryptMetadata(P.encrypted,this.sessionKey);r=JSON.stringify(w)}}catch(P){s.warn("[v1-bridge] Failed to decrypt orchestration prompt metadata; treating as malformed",{sessionId:e,error:String(P)});return}if(!r)return;let o;try{o=JSON.parse(r)}catch{return}if(o?.prompt_kind!==p.V1_ORCHESTRATION_PROMPT_KIND)return;let c=o.payload,a=c?.gateId,l=c?.taskId,d=c?.outcome?.round;if(typeof a!="string"||typeof l!="string"||typeof d!="number"){s.error("[v1-bridge] Orchestration prompt missing required payload fields",{sessionId:e,gateId:a,taskId:l,currentRound:d});return}if(i.externallyResolvedGateIds.has(a)){i.externallyResolvedGateIds.delete(a),s.info("[v1-bridge] Skipping orchestration prompt \u2014 gate externally resolved",{sessionId:e,gateId:a});return}i.pendingOrchestrationGate={gateId:a,taskId:l,currentRound:d,promptId:n.promptId??void 0},s.info("[v1-bridge] Pending orchestration gate captured",{sessionId:e,gateId:a,taskId:l,currentRound:d})}})}async subscribeToApplyUserDecision(e){let t=e.sessionId,n=await this.getOrCreateOrchestrationClient(e),i=o=>{let c=o.decision,a=this.activeSessions.get(t);if(!a)return;let l=mt(o.gateId,{originatedDecisions:a.originatedDecisions,externallyResolvedGateIds:a.externallyResolvedGateIds});if(l.kind==="unknown_gate"){s.warn("[v1-bridge] onApplyUserDecision event missing gateId",{sessionId:t,decision:c});return}a.originatedDecisions=l.originated,a.externallyResolvedGateIds=l.external;let d=l.gateId;if(l.kind==="own_echo"){s.info("[v1-bridge] applyUserDecision own-echo received",{sessionId:t,gateId:d,decision:c}),a.pendingOrchestrationGate?.gateId===d&&(a.pendingOrchestrationGate=void 0);return}a.pendingOrchestrationGate?.gateId===d&&(a.pendingOrchestrationGate=void 0),s.info("[v1-bridge] applyUserDecision foreign event \u2014 emitting banner",{sessionId:t,gateId:d,decision:c}),this.emitOrchestrationBanner(c)},r=null;try{r=await n.onApplyUserDecision(i),s.info("[v1-bridge] SDK onApplyUserDecision subscription started",{sessionId:t})}catch(o){throw s.warn("[v1-bridge] SDK onApplyUserDecision subscription failed",{sessionId:t,error:String(o)}),o}return{stop:()=>{if(r){try{r()}catch(o){s.warn("[v1-bridge] SDK onApplyUserDecision unsubscribe threw",{sessionId:t,error:String(o)})}r=null}}}}async getOrCreateOrchestrationClient(e){if(e.orchestrationClient)return e.orchestrationClient;let t=await p.keychainManager.getTokens((0,p.getEnvironment)());if(!t?.idToken)throw new Error("No Cognito ID token available \u2014 plugin must be logged in");if(!this.sessionKey)throw new Error("No session encryption key available \u2014 V1 bridge cannot start without sessionKey");let n=(0,p.getConfig)(),i=this.sessionKey,r=await lt.OrchestrationClient.connect({appsyncUrl:n.aws.appsyncUrl,cognitoIdToken:t.idToken,tokenProvider:async()=>(await p.keychainManager.getTokens((0,p.getEnvironment)()))?.idToken??t.idToken,sessionId:e.sessionId,sessionKeyB64:i,webSocketFactory:((o,c)=>new Ie.default(o,c))});return e.orchestrationClient=r,s.info("[v1-bridge] OrchestrationClient connected",{sessionId:e.sessionId}),r}async emitOrchestrationBanner(e){let t=process.env[j],n=ht(e);if(s.info(n),!t){s.warn("[v1-bridge] No tmux session \u2014 banner only logged, not displayed",{expectedEnv:j});return}try{await we(`tmux display-message -t "${t}" -- ${JSON.stringify(n)}`)}catch(i){s.warn("[v1-bridge] tmux display-message failed",{tmuxSession:t,error:String(i)})}}async handleOrchestrationPromptResponse(e,t,n){let i=e.pendingOrchestrationGate;if(!i)return!1;if(gt(e))return s.warn("[v1-bridge] Decision watcher absent \u2014 falling through to free-text routing",{sessionId:e.sessionId,gateId:i.gateId}),e.pendingOrchestrationGate=void 0,!1;if(e.externallyResolvedGateIds.has(i.gateId))return e.externallyResolvedGateIds.delete(i.gateId),e.pendingOrchestrationGate=void 0,s.info("[v1-bridge] User typed AFTER foreign decision \u2014 discarding without submit",{sessionId:e.sessionId,gateId:i.gateId}),await this.markEventExecuted(n),!0;let o=t.trim().match(/^(\d+)$/);if(!o)return await this.sendPromptError(e.sessionId,"Orchestration prompt requires a numeric option (1, 2, or 3)."),!0;let c=parseInt(o[1],10),a=(0,p.mapOptionToUserDecisionKind)(c);if(a===null)return await this.sendPromptError(e.sessionId,`Option ${c} is out of range for this orchestration prompt (1, 2, or 3).`),!0;e.originatedDecisions.add(i.gateId);try{await this.originateOrchestrationDecision(e,i,a),e.pendingOrchestrationGate=void 0;try{let l=this.encryptForEmit(`Orchestration decision submitted: ${a}`,{orchestrationDecision:!0,gateId:i.gateId,decision:a},p.EventType.NOTIFICATION,e.sessionId);l&&await this.appSyncClient.createEvent({sessionId:e.sessionId,type:p.EventType.NOTIFICATION,source:p.EventSource.DESKTOP,content:l.content,metadata:l.metadata,isEncrypted:l.isEncrypted,timestamp:(0,p.prepareEventTimestamp)({orderingKey:e.sessionId})})}catch(l){s.warn("[v1-bridge] Failed to emit decision-submitted notification",{sessionId:e.sessionId,error:String(l)})}await this.markEventExecuted(n)}catch(l){e.originatedDecisions.delete(i.gateId),s.error("[v1-bridge] applyUserDecision mutation failed; reverting state",{sessionId:e.sessionId,gateId:i.gateId,error:String(l)}),await this.sendPromptError(e.sessionId,`Failed to submit orchestration decision: ${l?.message??String(l)}`)}return!0}async originateOrchestrationDecision(e,t,n){let i=await this.getOrCreateOrchestrationClient(e),r=n;await i.applyUserDecision({taskId:t.taskId,gateId:t.gateId,currentRound:t.currentRound,decision:r}),s.info("[v1-bridge] applyUserDecision submitted",{sessionId:e.sessionId,gateId:t.gateId,decision:r})}openInlineSubscription(e){let{kind:t,sessionId:n,query:i,onPayload:r}=e,o=(0,p.getConfig)(),c=require("uuid").v4(),a=new URL(o.aws.appsyncUrl),d=/\.appsync-api\.[^.]+\.amazonaws\.com$/.test(a.host)?o.aws.appsyncUrl.replace("https://","wss://").replace("appsync-api","appsync-realtime-api"):`wss://${a.host}/graphql/realtime`,P=!1,w=null;return(async()=>{let g=await p.keychainManager.getTokens((0,p.getEnvironment)());if(!g?.idToken){s.warn(`[v1-bridge:${t}] No ID token \u2014 skipping subscription`,{sessionId:n});return}let m={host:a.host,Authorization:g.idToken},I=Buffer.from(JSON.stringify(m)).toString("base64"),h=Buffer.from(JSON.stringify({})).toString("base64"),y=`${d}?header=${I}&payload=${h}`;w=new Ie.default(y,["graphql-ws"]),w.on("open",()=>{P||(s.info(`[v1-bridge:${t}] WebSocket connected`,{sessionId:n}),w.send(JSON.stringify({type:"connection_init"})))}),w.on("message",async v=>{if(!P)try{let
|
|
25
|
+
}`,onPayload:async t=>{let n=t?.onEventCreated;if(!n||n.type!==p.EventType.INTERACTIVE_PROMPT)return;let i=this.activeSessions.get(e);if(!i)return;let r=n.metadata??void 0;if(n.isEncrypted&&r&&this.sessionKey)try{let P=JSON.parse(r);if(P&&typeof P=="object"&&typeof P.encrypted=="string"){let w=p.cryptoService.decryptMetadata(P.encrypted,this.sessionKey);r=JSON.stringify(w)}}catch(P){s.warn("[v1-bridge] Failed to decrypt orchestration prompt metadata; treating as malformed",{sessionId:e,error:String(P)});return}if(!r)return;let o;try{o=JSON.parse(r)}catch{return}if(o?.prompt_kind!==p.V1_ORCHESTRATION_PROMPT_KIND)return;let c=o.payload,a=c?.gateId,l=c?.taskId,d=c?.outcome?.round;if(typeof a!="string"||typeof l!="string"||typeof d!="number"){s.error("[v1-bridge] Orchestration prompt missing required payload fields",{sessionId:e,gateId:a,taskId:l,currentRound:d});return}if(i.externallyResolvedGateIds.has(a)){i.externallyResolvedGateIds.delete(a),s.info("[v1-bridge] Skipping orchestration prompt \u2014 gate externally resolved",{sessionId:e,gateId:a});return}i.pendingOrchestrationGate={gateId:a,taskId:l,currentRound:d,promptId:n.promptId??void 0},s.info("[v1-bridge] Pending orchestration gate captured",{sessionId:e,gateId:a,taskId:l,currentRound:d})}})}async subscribeToApplyUserDecision(e){let t=e.sessionId,n=await this.getOrCreateOrchestrationClient(e),i=o=>{let c=o.decision,a=this.activeSessions.get(t);if(!a)return;let l=mt(o.gateId,{originatedDecisions:a.originatedDecisions,externallyResolvedGateIds:a.externallyResolvedGateIds});if(l.kind==="unknown_gate"){s.warn("[v1-bridge] onApplyUserDecision event missing gateId",{sessionId:t,decision:c});return}a.originatedDecisions=l.originated,a.externallyResolvedGateIds=l.external;let d=l.gateId;if(l.kind==="own_echo"){s.info("[v1-bridge] applyUserDecision own-echo received",{sessionId:t,gateId:d,decision:c}),a.pendingOrchestrationGate?.gateId===d&&(a.pendingOrchestrationGate=void 0);return}a.pendingOrchestrationGate?.gateId===d&&(a.pendingOrchestrationGate=void 0),s.info("[v1-bridge] applyUserDecision foreign event \u2014 emitting banner",{sessionId:t,gateId:d,decision:c}),this.emitOrchestrationBanner(c)},r=null;try{r=await n.onApplyUserDecision(i),s.info("[v1-bridge] SDK onApplyUserDecision subscription started",{sessionId:t})}catch(o){throw s.warn("[v1-bridge] SDK onApplyUserDecision subscription failed",{sessionId:t,error:String(o)}),o}return{stop:()=>{if(r){try{r()}catch(o){s.warn("[v1-bridge] SDK onApplyUserDecision unsubscribe threw",{sessionId:t,error:String(o)})}r=null}}}}async getOrCreateOrchestrationClient(e){if(e.orchestrationClient)return e.orchestrationClient;let t=await p.keychainManager.getTokens((0,p.getEnvironment)());if(!t?.idToken)throw new Error("No Cognito ID token available \u2014 plugin must be logged in");if(!this.sessionKey)throw new Error("No session encryption key available \u2014 V1 bridge cannot start without sessionKey");let n=(0,p.getConfig)(),i=this.sessionKey,r=await lt.OrchestrationClient.connect({appsyncUrl:n.aws.appsyncUrl,cognitoIdToken:t.idToken,tokenProvider:async()=>(await p.keychainManager.getTokens((0,p.getEnvironment)()))?.idToken??t.idToken,sessionId:e.sessionId,sessionKeyB64:i,webSocketFactory:((o,c)=>new Ie.default(o,c))});return e.orchestrationClient=r,s.info("[v1-bridge] OrchestrationClient connected",{sessionId:e.sessionId}),r}async emitOrchestrationBanner(e){let t=process.env[j],n=ht(e);if(s.info(n),!t){s.warn("[v1-bridge] No tmux session \u2014 banner only logged, not displayed",{expectedEnv:j});return}try{await we(`tmux display-message -t "${t}" -- ${JSON.stringify(n)}`)}catch(i){s.warn("[v1-bridge] tmux display-message failed",{tmuxSession:t,error:String(i)})}}async handleOrchestrationPromptResponse(e,t,n){let i=e.pendingOrchestrationGate;if(!i)return!1;if(gt(e))return s.warn("[v1-bridge] Decision watcher absent \u2014 falling through to free-text routing",{sessionId:e.sessionId,gateId:i.gateId}),e.pendingOrchestrationGate=void 0,!1;if(e.externallyResolvedGateIds.has(i.gateId))return e.externallyResolvedGateIds.delete(i.gateId),e.pendingOrchestrationGate=void 0,s.info("[v1-bridge] User typed AFTER foreign decision \u2014 discarding without submit",{sessionId:e.sessionId,gateId:i.gateId}),await this.markEventExecuted(n),!0;let o=t.trim().match(/^(\d+)$/);if(!o)return await this.sendPromptError(e.sessionId,"Orchestration prompt requires a numeric option (1, 2, or 3)."),!0;let c=parseInt(o[1],10),a=(0,p.mapOptionToUserDecisionKind)(c);if(a===null)return await this.sendPromptError(e.sessionId,`Option ${c} is out of range for this orchestration prompt (1, 2, or 3).`),!0;e.originatedDecisions.add(i.gateId);try{await this.originateOrchestrationDecision(e,i,a),e.pendingOrchestrationGate=void 0;try{let l=this.encryptForEmit(`Orchestration decision submitted: ${a}`,{orchestrationDecision:!0,gateId:i.gateId,decision:a},p.EventType.NOTIFICATION,e.sessionId);l&&await this.appSyncClient.createEvent({sessionId:e.sessionId,type:p.EventType.NOTIFICATION,source:p.EventSource.DESKTOP,content:l.content,metadata:l.metadata,isEncrypted:l.isEncrypted,timestamp:(0,p.prepareEventTimestamp)({orderingKey:e.sessionId})})}catch(l){s.warn("[v1-bridge] Failed to emit decision-submitted notification",{sessionId:e.sessionId,error:String(l)})}await this.markEventExecuted(n)}catch(l){e.originatedDecisions.delete(i.gateId),s.error("[v1-bridge] applyUserDecision mutation failed; reverting state",{sessionId:e.sessionId,gateId:i.gateId,error:String(l)}),await this.sendPromptError(e.sessionId,`Failed to submit orchestration decision: ${l?.message??String(l)}`)}return!0}async originateOrchestrationDecision(e,t,n){let i=await this.getOrCreateOrchestrationClient(e),r=n;await i.applyUserDecision({taskId:t.taskId,gateId:t.gateId,currentRound:t.currentRound,decision:r}),s.info("[v1-bridge] applyUserDecision submitted",{sessionId:e.sessionId,gateId:t.gateId,decision:r})}openInlineSubscription(e){let{kind:t,sessionId:n,query:i,onPayload:r}=e,o=(0,p.getConfig)(),c=require("uuid").v4(),a=new URL(o.aws.appsyncUrl),d=/\.appsync-api\.[^.]+\.amazonaws\.com$/.test(a.host)?o.aws.appsyncUrl.replace("https://","wss://").replace("appsync-api","appsync-realtime-api"):`wss://${a.host}/graphql/realtime`,P=!1,w=null;return(async()=>{let g=await p.keychainManager.getTokens((0,p.getEnvironment)());if(!g?.idToken){s.warn(`[v1-bridge:${t}] No ID token \u2014 skipping subscription`,{sessionId:n});return}let m={host:a.host,Authorization:g.idToken},I=Buffer.from(JSON.stringify(m)).toString("base64"),h=Buffer.from(JSON.stringify({})).toString("base64"),y=`${d}?header=${I}&payload=${h}`;w=new Ie.default(y,["graphql-ws"]),w.on("open",()=>{P||(s.info(`[v1-bridge:${t}] WebSocket connected`,{sessionId:n}),w.send(JSON.stringify({type:"connection_init"})))}),w.on("message",async v=>{if(!P)try{let b=JSON.parse(v.toString());switch(b.type){case"connection_ack":w.send(JSON.stringify({id:c,type:"start",payload:{data:JSON.stringify({query:i,variables:{sessionId:n}}),extensions:{authorization:m}}}));break;case"start_ack":s.info(`[v1-bridge:${t}] Subscription started`,{sessionId:n});break;case"data":try{await r(b.payload?.data)}catch(E){s.warn(`[v1-bridge:${t}] onPayload handler threw`,{sessionId:n,error:String(E)})}break;case"ka":break;case"error":s.warn(`[v1-bridge:${t}] Subscription error`,{sessionId:n,error:b.payload?.errors?.[0]?.message??"unknown"});break}}catch(b){s.warn(`[v1-bridge:${t}] Failed to parse message`,{sessionId:n,error:String(b)})}}),w.on("error",v=>{s.warn(`[v1-bridge:${t}] WebSocket error`,{sessionId:n,error:v.message})}),w.on("close",v=>{s.info(`[v1-bridge:${t}] WebSocket closed`,{sessionId:n,code:v})})})().catch(g=>{s.warn(`[v1-bridge:${t}] Initial open failed`,{sessionId:n,error:String(g)})}),{stop:()=>{if(P=!0,w){try{w.readyState===Ie.default.OPEN&&w.send(JSON.stringify({id:c,type:"stop"})),w.close(1e3)}catch{}w=null}}}}isSessionLimitExceeded(e){return this.getErrorMessage(e).includes("SESSION_LIMIT_EXCEEDED")}isUsageLimitExceeded(e){let t=this.getErrorMessage(e);return t.includes("MESSAGE_LIMIT_EXCEEDED")||t.includes("IMAGE_LIMIT_EXCEEDED")}getErrorMessage(e){if(e instanceof Error)return e.message;if(typeof e=="object"&&e!==null){let t=e;if(t.errors&&Array.isArray(t.errors))return t.errors.map(n=>n.message||"").join(" ");if(typeof t.message=="string")return t.message}return String(e)}displaySubscriptionLimitError(e,t){let n=this.getErrorMessage(e),i="",r=n.match(/for your (\w+) plan/i);r&&(i=` (${r[1]} tier)`);let o="",c=n.match(/of (\d+)/);switch(c&&(o=` [Limit: ${c[1]}]`),console.log(`
|
|
26
26
|
`+"=".repeat(60)),console.log("\u26A0\uFE0F SUBSCRIPTION LIMIT REACHED"),console.log("=".repeat(60)),t){case"session":console.log(`You have reached the maximum number of active sessions${i}.`),console.log(`${o}`),console.log(`
|
|
27
27
|
To continue, please:`),console.log(" \u2022 Close an existing Claude Code session, or"),console.log(" \u2022 Upgrade your subscription in the CodeVibe iOS app");break;case"message":console.log(`You have reached your monthly message limit${i}.`),console.log(`${o}`),console.log(`
|
|
28
28
|
To continue, please:`),console.log(" \u2022 Wait until your usage resets next month, or"),console.log(" \u2022 Upgrade your subscription in the CodeVibe iOS app");break;case"image":console.log(`You have reached your monthly image attachment limit${i}.`),console.log(`${o}`),console.log(`
|