@quantiya/codevibe-claude-plugin 2.0.55 → 2.0.57

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codevibe-claude",
3
- "version": "2.0.55",
3
+ "version": "2.0.57",
4
4
  "description": "Sync Claude Code sessions with iOS mobile app via AWS backend. Control Claude Code from your phone with real-time bidirectional synchronization.",
5
5
  "author": {
6
6
  "name": "CodeVibe Team"
package/dist/server.js CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  `):typeof t=="string"?t.trim():"",n={uuid:e.uuid,timestamp:e.timestamp,text:i};return typeof e.error=="string"&&e.error.trim().length>0&&(n.error=e.error.trim()),typeof e.apiErrorStatus=="number"&&Number.isFinite(e.apiErrorStatus)&&(n.apiErrorStatus=e.apiErrorStatus),typeof e.apiErrorIsTransient=="boolean"&&(n.apiErrorIsTransient=e.apiErrorIsTransient),n}function gt(l){let e=l.text.length>0?l.text:mt,t=l.error;if(!t)return lt(e);let i=t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),n=new RegExp(`(^|[^A-Za-z0-9_])${i}($|[^A-Za-z0-9_])`).test(e);return lt(n?e:`${e} (${t})`)}function yt(l,e){let t=Date.parse(l.timestamp);return Number.isFinite(t)&&Number.isFinite(e)&&t>=e}var vt=3e3,jt=8,zt=10,Yt=100,Xt=3e3;function wt(l){let e=Buffer.byteLength(l.carryover);return{status:"partial",progressKey:`${l.fileIdentity.dev}:${l.fileIdentity.ino}:${l.size}:${l.endOfComplete}:${e}`,size:l.size,carryoverBytes:e,endOfComplete:l.endOfComplete}}var Jt="tool_activity",Te=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,i={}){let n=i.hostingSinceMs;this.hostingSinceMs=typeof n=="number"&&Number.isFinite(n)?n:Date.now(),this.relayedApiErrorUuids.clear(),this.backendSessionId=e,this.transcriptPath=t??null;let r=new B.ToolActivityOutbox({agent:"claude",logger:this.deps.logger,root:this.deps.root});this.ledger=new B.ToolActivityLedger({agent:"claude",sessionId:e,logger:this.deps.logger,root:this.deps.root}),t&&(this.cursor=new B.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 B.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,i={id:e.toolUseId,tool:e.toolName,normalizedTarget:It(e.toolName,e.toolInput),ts:e.ts??new Date().toISOString(),byteOffset:0,...e.toolInput!==void 0?{digest:(0,B.digestOf)(e.toolInput)}:{}},n=await t.observe(i);return n==="closed"?await t.captureTerminal(i,this.deps.shutdownDrainMs??vt)?"sealed-terminal":"closed":n}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,i=this.cursor;if(!t||!i)return"caught-up";this.ledger&&await this.ledger.reloadLegacyOwned();for(let n=0;n<jt;n+=1){let r=await i.readFrames();if(r.missing)return"caught-up";if(r.frames.length===0)return r.carryover.length>0?wt(r):"caught-up";let o=Number.POSITIVE_INFINITY,c=e?{includeInFlight:!0}:void 0,a=[];for(let u of r.frames){for(let b of Zt(u.line,u.startOffset))await t.observe(b,c)==="deferred-in-flight"&&(o=Math.min(o,u.startOffset));let d=ft(u.line);d&&yt(d,this.hostingSinceMs)&&a.push({err:d,offset:u.startOffset})}if(await t.flush(),await this.relayApiErrors(a),o<r.endOfComplete)return await i.checkpoint(o,{fileIdentity:r.fileIdentity,size:r.size,carryover:""}),this.pruneRelayedApiErrors(o),"more";if(await i.checkpoint(r.endOfComplete,{fileIdentity:r.fileIdentity,size:r.size,carryover:r.carryover}),this.pruneRelayedApiErrors(r.endOfComplete),!r.hasMore)return r.carryover.length>0?wt(r):"caught-up"}return"more"}hostingSince(){return this.hostingSinceMs}async relayApiErrors(e){let t=this.deps.onTranscriptApiError,i=this.backendSessionId;if(!t||!i||e.length===0)return;let n=this.deps.apiErrorRelayAwaitMs??Xt;for(let{err:r,offset:o}of e){if(this.relayedApiErrorUuids.has(r.uuid))continue;let c,a=new Promise(u=>{c=setTimeout(()=>u("timeout"),n),c.unref?.()});try{let u=await Promise.race([t(i,r),a]);if(u==="dropped"){this.deps.logger.warn("[api-error-relay] relay DROPPED fail-closed by the server (not delivered)",{sessionId:i,uuid:r.uuid,error:r.error});continue}this.relayedApiErrorUuids.set(r.uuid,o),u==="timeout"?this.deps.logger.warn("[api-error-relay] relay sink still pending past the await bound; continuing",{sessionId:i,uuid:r.uuid,error:r.error,awaitMs:n}):this.deps.logger.info("[api-error-relay] relayed transcript API error",{sessionId:i,uuid:r.uuid,error:r.error,apiErrorStatus:r.apiErrorStatus})}catch(u){this.deps.logger.warn("[api-error-relay] relay sink failed",{sessionId:i,uuid:r.uuid,error:r.error,err:String(u)})}finally{c&&clearTimeout(c)}}}pruneRelayedApiErrors(e){for(let[t,i]of this.relayedApiErrorUuids)i<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??Yt)),t=null,i=0;for(;;){let n=await this.doTranscriptBackstop(!0);if(n==="caught-up")break;if(typeof n!="string"){if(n.progressKey===t?i+=1:(t=n.progressKey,i=1),i>=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:n.size,carryoverBytes:n.carryoverBytes,endOfComplete:n.endOfComplete,unchangedTurns:i});break}await new Promise(r=>setTimeout(r,zt))}else t=null,i=0,await new Promise(r=>setImmediate(r))}await this.consolidator?.drainSends(this.deps.shutdownDrainMs??vt).catch(()=>{}),await this.consolidator?.shutdown(),this.consolidator=null,this.ledger=null,this.cursor=null}buildTransport(){let e=async(t,i,n)=>{let r=n?n.sessionKey:this.deps.getSessionKey();if(!r)throw new Error("[tool-activity] no session key at send (retryable)");let o=n?n.sessionKeyGen:this.deps.getSessionKeyGen();await this.deps.createEvent({sessionId:t,type:B.EventType.TOOL_ACTIVITY,source:B.EventSource.DESKTOP,content:this.deps.encryptContent(Jt,r),metadata:{encrypted:this.deps.encryptMetadata({manifest:i.manifest},r)},timestamp:i.windowStart,isEncrypted:!0,clientEventId:i.clientEventId,...o?{expectedSessionKeyGen:o}:{}})};return{send:async(t,i)=>{try{await e(t,i)}catch(n){if((0,B.isSessionKeyStaleError)(n)&&this.deps.refreshSessionKey){let r=await this.deps.refreshSessionKey(t);if(!r)throw n;await e(t,i,r);return}throw n}}}}};function Zt(l,e){let t;try{t=JSON.parse(l)}catch{return[]}let i=t?.message?.content;if(t?.type!=="assistant"||!Array.isArray(i))return[];let n=[];for(let r of i)r?.type==="tool_use"&&typeof r.id=="string"&&typeof r.name=="string"&&n.push({id:r.id,tool:r.name,normalizedTarget:It(r.name,r.input),ts:typeof t.timestamp=="string"?t.timestamp:new Date(0).toISOString(),byteOffset:e,...r.input!==void 0?{digest:(0,B.digestOf)(r.input)}:{}});return n}function It(l,e){if(!e||typeof e!="object")return;let t=e,i=n=>typeof n=="string"&&n.length>0?n:void 0;return i(t.file_path)??i(t.path)??i(t.pattern)??i(t.notebook_path)??i(t.url)??(typeof t.command=="string"?t.command.trim().split(/\s+/)[0]:void 0)}async function Ae(l,e,t){let i;try{let n=(()=>{try{return Promise.resolve(l())}catch(c){return Promise.reject(c)}})(),r=new Promise(c=>{i=setTimeout(()=>c("timeout"),e),i.unref&&i.unref()}),o=await Promise.race([n.then(()=>"settled",()=>"settled"),r]);if(o==="timeout"){n.catch(()=>{});try{t?.()}catch{}}return o}finally{i&&clearTimeout(i)}}function Be(l,e,t=25e3,i=()=>!0){let n=setTimeout(()=>{try{e()}catch{}let r=!1;try{r=i()}catch{r=!1}l(r?0:1)},t);return()=>clearTimeout(n)}var Oe=(0,Ge.promisify)(St.exec),en=(0,Ge.promisify)(Fe.execFile),tn="/exit",ie="CODEVIBE_TMUX_SESSION",nn=1e3,rn=1e4,Re=3,sn=100;function Et(l,e){let t=l?.[e];return typeof t=="string"?t:Buffer.isBuffer(t)?t.toString("utf8"):""}function kt(l){if(typeof l?.code!="number")return"unknown";let e=`${Et(l,"stderr")}
5
5
  ${Et(l,"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 on=1800*1e3,an=60*1e3,cn=1440*60*1e3;async function dn(l,e){let t=async(i,n)=>{try{await Oe(i)}catch(r){s.warn("tmux send-keys failed during self-terminate",{sessionName:l,label:n,error:String(r)})}};await t(`tmux send-keys -t "${l}" C-c`,"ctrl-c"),await new Promise(i=>setTimeout(i,200)),await t(`tmux send-keys -t "${l}" -l "${e}"`,"quit-text"),await new Promise(i=>setTimeout(i,500)),await t(`tmux send-keys -t "${l}" Enter`,"enter")}var pn={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},un=500,Pt=3,ln=2,mn=120,hn=/\x1B(?:\[[?!0-9;]*[A-Za-z]|\][^\x07\x1B]*(?:\x07|\x1B\\)|[()][A-B012])/g;function fn(l){let e=l.length,t=e>=2,i=l.slice(1);return t&&i.push({...pn}),{questionCount:e,hasReviewScreen:t,remainingQueue:i}}var Ue=class l{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 ve(s);this.sessionStartChain=Promise.resolve();this.sessionLifecycleQueueDepth=0;this.retiredSessionRecoveryPromises=new Map;this.lifecycleGeneration=0;this.isStopping=!1;this.teardownStepsTimedOut=[];this.pendingRetirements=new Set;this.retirementPhaseAbandoned=!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 Ie(s);this.queuedMobilePrompts=new Map;this.queuedMobilePromptDrains=new Set;this.queuedMobilePromptTimers=new Map;this.queuedAckRepairs=new Set;this.queuedAckFailures=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 Pe(()=>this.getDaemonHealth()),this.commandExecutor=new Se,this.promptResponder=new ke,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,i){let{record:n}=(0,p.newPromptRaise)({sessionId:e,producerKind:"PLUGIN_APPROVAL",mobileActionable:t,agentType:"CLAUDE",...i!==void 0&&{title:i}});return this.e1PromptRaises.set(n.promptId,{record:n}),n}stashE1RaiseContent(e,t,i){let n=this.e1PromptRaises.get(e);n&&(n.contentPlain=t,n.metadataPlain=i)}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(i=>i.record.sessionId===e&&this.promptPublications.isPublishing(i.record.promptId));if(t.length===0)return this.snapshotOpenE1(e);await Promise.all(t.map(i=>this.promptPublications.waitFor(i.record.promptId)))}}async reRaiseSnapshotE1(e,t,i){if(e.length===0)return[];s.info("E1: re-raising open prompts onto replacement session",{replacementSessionId:t,count:e.length});let n=[];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,i?{untilAck:i}:void 0)&&!o.resolutionStarted&&n.push(r)}return n}parkE1Orphans(e,t){if(t.length===0)return;let i=new Map;for(let n of this.e1ParkedOrphans.get(e)??[])i.set(n.record.promptId,n);for(let n of t)i.set(n.record.promptId,n);this.e1ParkedOrphans.set(e,[...i.values()]),s.info("E1: parked retired-session open prompts for replacement re-home",{claudeSessionId:e,count:i.size})}async drainParkedE1Orphans(e,t,i){let n=this.e1ParkedOrphans.get(e);if(!n||n.length===0)return;let r=await this.reRaiseSnapshotE1(n,t,i);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,i){let n=t.record;if(this.e1NeedsResolution.has(n.promptId)||!(0,p.canRecoverPromptRaise)(t,this.promptPublications.isPublishing(n.promptId)))return!1;let r=n.mobileActionable&&t.contentPlain!==void 0,o=i?.untilAck,c=4;for(let a=1;;a++){if(!(0,p.isOpenPromptRaise)(t,this.e1PromptRaises.get(n.promptId),this.e1NeedsResolution.has(n.promptId)))return!1;if(o&&!o())return this.e1NeedsReRaise.add(n.promptId),s.info("E1: re-raise wait aborted (lifecycle superseded/stopping) \u2014 flagged for TTL-drain",{sessionId:e,promptId:n.promptId}),!1;try{if(r){let u=this.encryptForEmit(t.contentPlain,t.metadataPlain??{},p.EventType.INTERACTIVE_PROMPT,e);if(!u)return!1;await this.promptPublications.run(n.promptId,()=>this.appSyncClient.createEvent({sessionId:e,type:p.EventType.INTERACTIVE_PROMPT,source:p.EventSource.DESKTOP,content:u.content,metadata:u.metadata,...(0,p.raiseFieldsFromRecord)(n),timestamp:(0,p.prepareEventTimestamp)({orderingKey:e}),isEncrypted:u.isEncrypted?!0:void 0}))}else if(n.mobileActionable=!1,!await this.emitSuppressedPromptCarrier(e,n))return!1;return this.e1NeedsReRaise.delete(n.promptId),s.info("E1: re-raised prompt onto replacement session",{sessionId:e,promptId:n.promptId,actionable:r}),!0}catch(u){if(!o&&a>=c)return this.e1NeedsReRaise.add(n.promptId),s.error("E1: re-raise failed after retries \u2014 flagged for TTL-drain re-attempt",{sessionId:e,promptId:n.promptId,error:u instanceof Error?u.message:String(u)}),!1;if(o){let d=Date.now()+Math.min(250*2**(a-1),4e3);for(;Date.now()<d&&o();)await new Promise(b=>setTimeout(b,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 i=this.e1ResolutionInFlight.get(t);if(i)return i;let n=this.e1PromptRaises.get(t);if(!n)return this.e1NeedsResolution.delete(t),this.e1NeedsReRaise.delete(t),!0;n.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:n.record.sessionId||e,type:p.EventType.NOTIFICATION,source:p.EventSource.DESKTOP,resolvedPromptId:t,ownerToken:n.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 n=new Set(e.pendingAskUserQuestionInvocations?.map(r=>r.fingerprint)??[]);if(e.activeAskUserQuestionInvocation?.fingerprint&&n.add(e.activeAskUserQuestionInvocation.fingerprint),n.size>0){e.completedAskUserQuestionFingerprints||(e.completedAskUserQuestionFingerprints=new Set);for(let r of n)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,i){t&&this.encryptedKeyRecovery?.claudeSessionId!==t||i&&this.encryptedKeyRecovery?.backendSessionId!==i||(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 i=e.session_id,n=this.encryptedKeyRecovery;return!n||n.claudeSessionId!==i||n.backendSessionId!==t?(this.clearEncryptedKeyRecovery("replaced by a different recovery target"),this.encryptedKeyRecovery={claudeSessionId:i,backendSessionId:t,payload:e,livenessInvalidated:!1}):(n.payload=e,n.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(i=>setTimeout(i,1e3))}return!1}scheduleEncryptedKeyRecovery(e,t){if(this.isStopping)return;let i=e.session_id,n=this.encryptedKeyRecovery;if(!n||n.claudeSessionId!==i||n.backendSessionId!==t){let c=this.enterEncryptedKeyRecovery(e,t);this.invalidateEncryptedRecoveryLiveness(c);return}else n.payload=e;if(!n.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:i,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:i,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 i=this.pendingMobilePrompts.get(e);if(!i)return!1;let n=Date.now(),r=t.trim(),o=[],c=!1;for(let a of i)if(!(n-a.timestamp>l.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=H.join(J.tmpdir(),`codevibe-claude-${e}.port`);try{W.writeFileSync(t,this.assignedPort.toString()),s.info(`Port file written: ${t} -> ${this.assignedPort}`)}catch(i){s.error(`Failed to write port file: ${t}`,i)}}removePortFile(e){let t=H.join(J.tmpdir(),`codevibe-claude-${e}.port`);try{W.existsSync(t)&&(W.unlinkSync(t),s.info(`Port file removed: ${t}`))}catch(i){s.warn(`Failed to remove port file: ${t}`,i)}}hasOtherLiveDaemonForSession(e){try{let t=(0,Fe.execSync)("ps -eww -o pid= -o args=",{encoding:"utf8",timeout:2e3}),i=process.pid;for(let n of t.split(`
6
- `)){let r=n.trim();if(!r)continue;let o=r.indexOf(" ");if(o<0)continue;let c=parseInt(r.substring(0,o),10);if(isNaN(c)||c===i)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{s.info("Starting CodeVibe MCP Server...",{environment:(0,p.getEnvironment)()}),this.appSyncClient=new p.AppSyncClient;let e=Me();if(e.size>0&&s.warn("DEV FAULTS ACTIVE (development only) \u2014 failure paths will be exercised on purpose",{faults:Object.fromEntries(e)}),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(i=>{s.warn("Failed to update available agents (non-fatal)",{error:i?.message})});try{let i=await this.appSyncClient.sweepOrphanSessions({agentType:"CLAUDE"});i>0&&s.info("Orphan sweep: marked stale Claude sessions INACTIVE",{swept:i})}catch(i){s.warn("Orphan sweep failed, continuing startup",{error:i instanceof Error?i.message:String(i)})}}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(cn,Math.max(an,e)):on;this.portRefreshTimer=setInterval(()=>this.refreshPortFiles(),t),this.e1TtlRefreshTimer&&clearInterval(this.e1TtlRefreshTimer),this.e1TtlRefreshTimer=setInterval(()=>{this.refreshE1Ttls()},l.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 i of t){let n=this.e1PromptRaises.get(i);n&&this.activeSessions.has(n.record.sessionId)&&await this.reRaiseOneE1(n.record.sessionId,n)}}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 i of this.activeSessions.values()){let n=H.join(J.tmpdir(),`codevibe-claude-${i.claudeSessionId}.port`);try{W.utimesSync(n,e,e)}catch(o){o?.code==="ENOENT"?this.writePortFile(i.claudeSessionId):s.warn(`Port-file refresh failed: ${n}`,o)}let r=H.join(J.tmpdir(),`codevibe-claude-${i.claudeSessionId}.pid`);try{W.utimesSync(r,e,e)}catch(o){o?.code!=="ENOENT"&&s.warn(`Pid-file refresh failed: ${r}`,o)}}let t=process.env[ie];if(t){let i=H.join(J.tmpdir(),`codevibe-claude-instance-${t}.json`);try{W.utimesSync(i,e,e)}catch(n){n?.code!=="ENOENT"&&s.warn(`Instance-map refresh failed: ${i}`,n)}}}startTmuxLifecycleMonitor(){if(this.tmuxLifecycleTimer)return;let e=process.env[ie];e&&(this.tmuxLifecycleInitDeadlineMs=Date.now()+rn,this.tmuxLifecycleTimer=setInterval(()=>{this.checkTmuxLifecycle(e)},nn),this.tmuxLifecycleTimer.unref?.(),this.checkTmuxLifecycle(e))}async tmuxSessionState(e){try{return await en("tmux",["has-session","-t",e]),"alive"}catch(t){let i=kt(t);return i==="absent"?i:(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 i=this.tmuxLifecycleObservedAlive||Date.now()>=this.tmuxLifecycleInitDeadlineMs;if(t==="unknown"||!i||this.isStopping)return;this.stopTmuxLifecycleMonitor(),s.info("Native Claude tmux session ended; stopping companion daemon",{tmuxSession:e});let n=Be(r=>process.exit(r),()=>s.error("Shutdown watchdog fired after native session end; exiting anyway",{watchdogMs:25e3,timedOutSteps:this.teardownStepsTimedOut,durableObligationSettled:this.isDurableObligationSettled(),unretired:Array.from(this.pendingRetirements)}),25e3,()=>this.isDurableObligationSettled());this.stop().then(()=>{n(),process.exit(0)},r=>{n(),s.error("Failed to stop companion daemon after native session end",{error:r instanceof Error?r.message:String(r)}),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[ie];if(!t)return!0;let i=await this.tmuxSessionState(t);return i==="absent"?!0:(s.info("Ignoring SIGHUP while native Claude tmux session remains available",{tmuxSession:t,state:i}),!1)}stop(){return this.stopPromise?this.stopPromise:(this.stopPromise=this.doStop(),this.stopPromise)}async doStop(){if(s.info("Stopping MCP Server..."),this.isStopping=!0,this.stopTmuxLifecycleMonitor(),this.lifecycleGeneration+=1,this.clearEncryptedKeyRecovery("daemon stopping"),this.teardownStepsTimedOut=[],this.retirementPhaseAbandoned=!1,await this.boundedTeardownStep("sessionStartChain.join",()=>this.sessionStartChain.catch(()=>{})),this.portRefreshTimer&&(clearInterval(this.portRefreshTimer),this.portRefreshTimer=null),this.e1TtlRefreshTimer&&(clearInterval(this.e1TtlRefreshTimer),this.e1TtlRefreshTimer=null),this.toolActivity){let c=this.toolActivity,a,u=!1,d=await Ae(async()=>{try{await c.stop()}catch(b){u=!0,a=b}},3e3,()=>s.warn("toolActivity.stop timed out \u2014 continuing teardown without it",{timeoutMs:3e3}));if(d==="timeout"&&this.teardownStepsTimedOut.push("toolActivity.stop"),u)throw a;d==="settled"&&(this.toolActivity=null)}let e=Array.from(this.activeSessions.keys()),t=new Set,i=[];s.info(`Marking ${e.length} active session(s) as INACTIVE...`);for(let c of e){let a=this.activeSessions.get(c);a?.mobileEndWatcher&&(a.mobileEndWatcher.stop(),a.mobileEndWatcher=void 0),this.appSyncClient.stopHeartbeat(c)}this.pendingRetirements=new Set(e),await Ae(async()=>{for(let c of e){if(this.retirementPhaseAbandoned)return;let a=this.activeSessions.get(c);try{if(a&&this.hasOtherLiveDaemonForSession(a.claudeSessionId)){s.info("Another daemon serves this session \u2014 skipping mark INACTIVE AND port file removal during shutdown",{sessionId:c,claudeSessionId:a.claudeSessionId,myPid:process.pid}),t.add(a.claudeSessionId),this.pendingRetirements.delete(c);continue}try{de("stall-retirement")&&(s.warn("[dev-fault] stall-retirement: this INACTIVE write will never settle",{sessionId:c}),await new Promise(()=>{})),await this.retireHostedSession(c),this.pendingRetirements.delete(c),s.info("Session marked as INACTIVE during shutdown",{sessionId:c})}catch(u){i.push(c),s.error("Failed to mark session as INACTIVE during shutdown",{sessionId:c,error:u})}a&&this.removeOwnedSessionFiles(a.claudeSessionId)}catch(u){s.warn("Failed during shutdown cleanup",{sessionId:c,error:u})}}},8e3,()=>{this.retirementPhaseAbandoned=!0,s.warn("session retirement phase timed out \u2014 continuing teardown",{timeoutMs:8e3,unretired:Array.from(this.pendingRetirements)})})==="timeout"&&this.teardownStepsTimedOut.push("retirementPhase"),this.appSyncClient.cleanupSubscriptions(),this.activeSessions.clear(),await this.boundedTeardownStep("httpApi.stop",()=>this.httpApi.stop({protectedSessionIds:t}));let r=process.env[ie];r&&t.size===0&&this.removeOwnedTmuxFiles(r),s.info("MCP Server stopped");let o=Array.from(this.pendingRetirements);if(i.length>0||o.length>0)throw new Error(`Failed to retire hosted Claude sessions: ${(i.length>0?i:o).join(", ")}${this.retirementPhaseAbandoned?" (retirement phase timed out)":""}`)}async boundedTeardownStep(e,t){await Ae(async()=>{try{await t()}catch(n){s.warn(`${e} failed`,{error:String(n)})}},3e3,()=>s.warn(`${e} timed out \u2014 continuing teardown without it`,{timeoutMs:3e3}))==="timeout"&&this.teardownStepsTimedOut.push(e)}isDurableObligationSettled(){return this.pendingRetirements.size===0}isDurableObligationSettledForExit(){return this.isDurableObligationSettled()}getTeardownStepsTimedOut(){return[...this.teardownStepsTimedOut]}getPendingRetirements(){return Array.from(this.pendingRetirements)}async retireHostedSession(e){let t;for(let i=1;i<=Re;i+=1)try{await this.appSyncClient.updateSession({sessionId:e,status:p.SessionStatus.INACTIVE});return}catch(n){t=n,s.warn("Failed to retire hosted Claude session",{sessionId:e,attempt:i,maxAttempts:Re,error:n instanceof Error?n.message:String(n)}),i<Re&&await new Promise(r=>setTimeout(r,sn*i))}throw new Error(`Failed to retire hosted Claude session ${e} after ${Re} attempts: ${String(t)}`)}removeOwnedSessionFiles(e){this.removePortFile(e),this.removeOwnedFile(H.join(J.tmpdir(),`codevibe-claude-${e}.pid`)),this.removeOwnedFile(H.join(J.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 i=t[1];this.removeOwnedFile(H.join(J.tmpdir(),`codevibe-claude-instance-${e}.json`)),this.removeOwnedFile(H.join(J.tmpdir(),`codevibe-claude-root-${e}.id`)),this.removeOwnedFile(H.join(J.tmpdir(),`codevibe-claude-tmux-session-${i}`))}removeOwnedFile(e){try{W.unlinkSync(e)}catch(t){t?.code!=="ENOENT"&&s.warn(`Failed to remove owned Claude lifecycle file: ${e}`,t)}}encryptForEmit(e,t,i,n){let r=this.activeSessions.get(n)?.encryptionSnapshot;if(!r)return s.error("No encryption snapshot for target session \u2014 dropping event (fail-closed, #638 H1)",{type:i,sessionId:n,sessionKnown:this.activeSessions.has(n)}),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:i,sessionId:n,encryptionResolved:r.encryptionResolved}),o}async handleEventFromHook(e){let{session_id:t,hook_event_name:i,type:n,content:r}=e;if(s.info("Processing hook event",{sessionId:t,hookEvent:i,type:n}),i==="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:i,owningSessionId:this.currentClaudeSessionId});return}let o,c;try{if(i==="SessionStart")await this.handleSessionStart(e);else if(i==="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:i});let g=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},g,"self-heal");let y=this.claudeToBackendSessionId.get(t)||this.generateBackendSessionId(t);if(!this.activeSessions.has(y)){s.warn("Dropping hook because Claude self-heal lifecycle intent is stale",{claudeSessionId:t,hookEvent:i});return}}let a=this.claudeToBackendSessionId.get(t)||this.generateBackendSessionId(t);if(i==="PostToolUse"&&e.metadata?.tool_name==="AskUserQuestion"){let g=this.activeSessions.get(a),y=this.computeAskUserQuestionFingerprint(e.tool_input?.questions),h=g?.pendingAskUserQuestionInvocations,v=y?h?.findIndex(I=>I.fingerprint===y)??-1:-1;if(g&&h&&v>=0){let[I]=h.splice(v,1);I.toolUseId=e.tool_use_id;let S=g.activeAskUserQuestionInvocation?.occurrenceId===I.occurrenceId;S&&(g.activeAskUserQuestionFingerprint&&(g.completedAskUserQuestionFingerprints||(g.completedAskUserQuestionFingerprints=new Set),g.completedAskUserQuestionFingerprints.add(g.activeAskUserQuestionFingerprint)),this.clearPromptState(g),I.completionEpoch=g.promptStateEpoch,g.activeAskUserQuestionInvocation=void 0);for(let E of I.e1PromptIds)await this.resolveE1PluginApproval(a,E);this.scheduleQueuedMobilePromptDrain(a),s.info("AskUserQuestion resolved by PostToolUse hook",{sessionId:a,occurrenceId:I.occurrenceId,toolUseId:I.toolUseId,promptIds:I.e1PromptIds,clearedCurrentPrompt:S})}else s.warn("Ignoring uncorrelated AskUserQuestion PostToolUse hook",{sessionId:a,toolUseId:e.tool_use_id,completionFingerprint:y?.slice(0,16)})}if(this.toolActivity?.isActive()&&(n===p.EventType.USER_PROMPT||n===p.EventType.ASSISTANT_RESPONSE||n===p.EventType.INTERACTIVE_PROMPT||n===p.EventType.NOTIFICATION)&&await this.toolActivity.flush().catch(g=>s.warn("[tool-activity] boundary flush failed",{sessionId:a,err:String(g)})),i==="UserPromptSubmit"){let g=this.activeSessions.get(a);if(g&&await this.clearInteractivePromptsForAcceptedInput(g),g?.completedAskUserQuestionFingerprints?.size){let y=g.completedAskUserQuestionFingerprints.size;g.completedAskUserQuestionFingerprints.clear(),s.info("Turn boundary \u2014 cleared closed-AskUserQuestion fingerprints",{sessionId:a,clearedCount:y})}}if(n===p.EventType.USER_PROMPT&&e.source===p.EventSource.DESKTOP&&i==="UserPromptSubmit"&&r&&this.isRecentMobilePrompt(a,r)){s.info("Skipping duplicate USER_PROMPT from mobile-originated prompt",{sessionId:a,contentLength:r.length});return}if(n===p.EventType.INTERACTIVE_PROMPT){(typeof e.prompt_id!="string"||e.prompt_id.length===0)&&(e.prompt_id=`synth-${(0,ae.randomUUID)()}`,s.info("Synthesized prompt_id for INTERACTIVE_PROMPT (hook omitted it)",{sessionId:a,synthesizedPromptId:e.prompt_id}));let g=this.activeSessions.get(a),y;if(g&&e.metadata?.tool_name==="AskUserQuestion"){if(y=this.computeAskUserQuestionFingerprint(e.metadata.tool_input?.questions),y){let S=g.activeAskUserQuestionFingerprint===y,E=g.completedAskUserQuestionFingerprints?.has(y)??!1;if(S||E){s.info("Dropping duplicate INTERACTIVE_PROMPT \u2014 AskUserQuestion already tracked",{sessionId:a,fingerprint:y.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,v=Array.isArray(h)&&h.some(S=>S?.multiSelect===!0),I=Array.isArray(h)&&(h[0]?.options?.length??0)===0;if(y&&(v||I)){g.completedAskUserQuestionFingerprints||(g.completedAskUserQuestionFingerprints=new Set),g.completedAskUserQuestionFingerprints.add(y);let S=v?"\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:y.slice(0,16),hasMultiSelect:v,isZeroExplicitOption:I,questionCount:Array.isArray(h)?h.length:0}),setImmediate(()=>this.emitDegradedAUQNotification(a,S));let E=this.newE1Raise(a,!1),F=this.registerAskUserQuestionInvocation(g,y,this.nextPromptGen++,E.promptId);g.activeAskUserQuestionInvocation=F,this.emitSuppressedPromptCarrier(a,E).catch(U=>{s.error("E1: failed to emit degraded-AUQ badge carrier",{sessionId:a,promptId:E.promptId,error:U instanceof Error?U.message:String(U)})});return}}if(g){let h=this.newE1Raise(a,!0);e.prompt_id=h.promptId,this.clearPromptState(g),g.waitingForPromptResponse=!0,g.pendingPromptId=e.prompt_id;let v=this.nextPromptGen++;if(g.promptGenerationToken={promptId:e.prompt_id||"",gen:v},y){g.activeAskUserQuestionFingerprint=y;let I=this.registerAskUserQuestionInvocation(g,y,v,h.promptId);g.activeAskUserQuestionInvocation=I}s.info("Interactive prompt detected - will parse options from tmux",{sessionId:a,promptId:e.prompt_id,tokenGen:v,askUserQuestionFingerprint:y?.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(n===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;n===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:n,sessionId:a,hasSessionKey:!!this.sessionKey,sessionKeyLength:this.sessionKey?.length||0});let d=this.encryptForEmit(r,e.metadata,n,a);if(!d)return;let b=d.content,m=d.metadata,w=d.isEncrypted,f=await this.appSyncClient.createEvent({sessionId:a,type:n,source:e.source,content:b,metadata:m,promptId:e.prompt_id,timestamp:(0,p.prepareEventTimestamp)({orderingKey:a}),isEncrypted:w?!0:void 0});if(o&&c&&((0,p.isPersistedEventResult)(f)?await o.finalizeLegacyOwned([c]):o.clearLegacyInFlight(c)),n===p.EventType.USER_PROMPT&&e.source===p.EventSource.DESKTOP){let g=this.activeSessions.get(a);g?.waitingForPromptResponse&&(this.promoteFingerprintAndClearPromptState(g),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 Te({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 i=gt(t),n={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(i,n,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 i=t.sessionKeyGen??null,n=t.encryptedKeys??[];if(n.length===0)return null;let r=await p.keychainManager.getSessionKey(e,n,i);return r?(s.info("[tool-activity] refreshed session key for SessionKeyStale retry (live key untouched)",{sessionId:e,sessionKeyGen:i}),{sessionKey:r,sessionKeyGen:i}):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,i="explicit"){if(this.isStopping)return Promise.resolve();let n=e.session_id,r=i==="explicit"?this.recordExplicitClaudeSessionStart(n):{ownershipGeneration:this.claudeOwnershipGeneration,sessionIntentVersion:this.getClaudeSessionIntentVersion(n)};return this.enqueueSessionLifecycle(async()=>{if(this.isStopping)return;if(i==="self-heal"){if(!this.canSelfHealClaudeSession(n,r)){s.warn("Rejected stale Claude self-heal request",{claudeSessionId:n,currentClaudeSessionId:this.currentClaudeSessionId});return}this.currentClaudeSessionId===null&&(this.currentClaudeSessionId=n,r.ownershipGeneration=++this.claudeOwnershipGeneration)}else if(!this.isClaudeStartIntentCurrent(n,r))return;let o=++this.lifecycleGeneration;await this.handleSessionStartSerialized(e,t,o,()=>this.isClaudeStartIntentCurrent(n,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 i of this.activeSessions.values())i.claudeSessionId!==e&&this.endedOrSupersededClaudeSessionIds.add(i.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(i=>i.claudeSessionId!==e)}enqueueSessionLifecycle(e){let t=this.sessionStartChain;this.sessionLifecycleQueueDepth+=1;let i=this.sessionLifecycleQueueDepth===1?e():t.catch(()=>{}).then(e),n=i.finally(()=>{this.sessionLifecycleQueueDepth-=1});return this.sessionStartChain=n.catch(()=>{}),i}async handleSessionStartSerialized(e,t,i=this.lifecycleGeneration,n=()=>!0){let r=()=>this.isLifecycleCurrent(i)&&n();if(!r())return;let o=e.session_id,c=this.appSyncClient.getCurrentUserId(),a=this.claudeToBackendSessionId.get(o),u=t||a?null:await this.sessionBindingStore.read(c,o);if(!r())return;let d=t??a??u??this.generateBackendSessionId(o),b=e.metadata?.cwd||process.cwd();this.claudeToBackendSessionId.set(o,d),s.info("Session started",{claudeSessionId:o,sessionId:d,cwd:b});let m=this.activeSessions.get(d);if(m){if(m.mobileEndWatcher?.stop(),m.mobileEndWatcher=void 0,m.orchestrationPromptWatcher?.stop(),m.orchestrationPromptWatcher=void 0,m.orchestrationDecisionWatcher?.stop(),m.orchestrationDecisionWatcher=void 0,m.orchestrationClient){try{await m.orchestrationClient.destroy()}catch(h){s.warn("[v1-bridge] OrchestrationClient.destroy() failed on same-session restart",{sessionId:d,error:String(h)})}m.orchestrationClient=void 0}this.appSyncClient.stopHeartbeat(d),this.appSyncClient.cleanupSubscription(d),this.activeSessions.get(d)===m&&this.activeSessions.delete(d)}let w=Array.from(this.activeSessions.keys()).filter(h=>h!==d);if(w.length>0){s.info(`Marking ${w.length} previous session(s) as INACTIVE`);for(let h of w){if(this.toolActivity&&this.toolActivity.sessionId===h){let I=this.toolActivity,S=!1;for(let E=1;E<=2&&!S;E+=1)try{await I.stop(),S=!0}catch(F){s.warn("[tool-activity] outgoing consolidator drain failed on resume",{prevId:h,attempt:E,error:String(F)})}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 v=this.activeSessions.get(h);if(v?.mobileEndWatcher&&(v.mobileEndWatcher.stop(),v.mobileEndWatcher=void 0),v?.orchestrationPromptWatcher&&(v.orchestrationPromptWatcher.stop(),v.orchestrationPromptWatcher=void 0),v?.orchestrationDecisionWatcher&&(v.orchestrationDecisionWatcher.stop(),v.orchestrationDecisionWatcher=void 0),v?.orchestrationClient){try{await v.orchestrationClient.destroy()}catch(I){s.warn("[v1-bridge] OrchestrationClient.destroy() failed on resume",{prevId:h,error:String(I)})}v.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(I){s.warn("Failed to mark previous session as INACTIVE",{prevId:h,error:I})}v&&this.removePortFile(v.claudeSessionId),this.activeSessions.delete(h)}}this.writePortFile(o);let f={sessionId:d,claudeSessionId:o,userId:c,projectPath:b,cwd:b,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,f.encryptionSnapshot={sessionKey:null,sessionIsEncrypted:!1,encryptionResolved:!1},this.activeSessions.set(d,f);try{let h;try{h=await(0,p.resumeOrCreateSession)({sessionId:d,userId:f.userId,agentType:p.AgentType.CLAUDE,projectPath:b,metadata:e.metadata||{}},this.appSyncClient,s)}catch(v){if(!(0,p.isRetiredSessionCannotReactivateError)(v))throw v;if(!r()||this.activeSessions.get(d)!==f){this.activeSessions.get(d)===f&&this.activeSessions.delete(d),this.claudeToBackendSessionId.get(o)===d&&this.claudeToBackendSessionId.delete(o),this.removePortFile(o);return}let I=d;this.appSyncClient.stopHeartbeat(I),this.appSyncClient.cleanupSubscription(I),this.activeSessions.delete(I);let S=Ne(c,o,I);if(await this.sessionBindingStore.write(c,o,S),!r())return;d=S,f.sessionId=d,this.activeSessions.set(d,f),this.claudeToBackendSessionId.set(o,d),s.warn("Backend rejected retired Claude generation during SessionStart; creating replacement row",{claudeSessionId:o,retiredSessionId:I,replacementSessionId:d}),h=await(0,p.resumeOrCreateSession)({sessionId:d,userId:f.userId,agentType:p.AgentType.CLAUDE,projectPath:b,metadata:e.metadata||{}},this.appSyncClient,s)}if(!r()){await this.retireCancelledClaudeBootstrap({requestedSessionId:d,authoritativeSessionId:h.sessionId,claudeSessionId:o,sessionState:f});return}if(this.activeSessions.get(d)!==f){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:f});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:f});return}this.activeSessions.get(d)===f&&this.activeSessions.delete(d),d=h.sessionId,f.sessionId=d,this.activeSessions.set(d,f),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,f.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:f});return}if(this.sessionKey){let v=this.buildToolActivityIntegration();try{if(await v.start(d,e.transcript_path,{hostingSinceMs:e.received_at_ms}),!r()){await v.stop().catch(()=>{}),await this.retireCancelledClaudeBootstrap({requestedSessionId:d,authoritativeSessionId:h.sessionId,claudeSessionId:o,sessionState:f});return}this.toolActivity=v}catch(I){s.warn("[tool-activity] consolidator start failed \u2014 keeping per-tool TOOL_USE",{sessionId:d,error:String(I)}),this.toolActivity=null,await v.stop().catch(()=>{})}}}catch(h){if(!r()){await this.retireCancelledClaudeBootstrap({requestedSessionId:d,authoritativeSessionId:this.authoritativeSessionIdFromError(h)??d,claudeSessionId:o,sessionState:f});return}let v=h?.authoritativeSessionId;if(typeof v=="string"&&v.length>0&&v!==d&&this.activeSessions.get(d)===f&&(this.activeSessions.delete(d),d=v,f.sessionId=d,this.activeSessions.set(d,f),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)===f&&this.activeSessions.delete(d),this.removePortFile(o);return}if(h?.code==="ENCRYPTED_SESSION_NO_KEY"){this.sessionIsEncrypted=!0,this.sessionKey=null,this.encryptionResolved=!0,f.encryptionSnapshot={sessionKey:null,sessionIsEncrypted:!0,encryptionResolved:!0};let I=this.enterEncryptedKeyRecovery(e,d);this.appSyncClient.cleanupSubscription(d),await this.invalidateEncryptedRecoveryLiveness(I),s.error("Failed to create/resume session:",h);return}else{this.activeSessions.get(d)===f&&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:f});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)===f&&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 v=this.enterEncryptedKeyRecovery(e,d);this.appSyncClient.cleanupSubscription(d),await this.invalidateEncryptedRecoveryLiveness(v);return}}if(!r()){await this.retireCancelledClaudeBootstrap({requestedSessionId:d,authoritativeSessionId:d,claudeSessionId:o,sessionState:f});return}let y=this.activeSessions.get(d);if(y){y.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(I){s.warn("Failed to mark session INACTIVE on mobile-end",{sessionId:d,error:I})}let v=process.env[ie];if(!v){s.warn("No tmux session set; skipping desktop self-terminate",{sessionId:d,expectedEnv:ie});return}await dn(v,tn)});let h=!1;try{y.orchestrationPromptWatcher=this.subscribeToOrchestrationPrompts(d)}catch(v){h=!0,s.warn("[v1-bridge] Failed to start orchestration prompt watcher",{sessionId:d,error:String(v)})}try{y.orchestrationDecisionWatcher=await this.subscribeToApplyUserDecision(y)}catch(v){h=!0,s.warn("[v1-bridge] Failed to start applyUserDecision watcher",{sessionId:d,error:String(v)})}h&&this.emitBridgeUnavailableBanner()}this.clearEncryptedKeyRecovery("session key recovered and liveness established",o,d)}recoverRetiredBackendSession(e,t){let i=this.retiredSessionRecoveryPromises.get(e);if(i)return i;if(this.isStopping)return Promise.resolve();let n=t.claudeSessionId,r={ownershipGeneration:this.claudeOwnershipGeneration,sessionIntentVersion:this.getClaudeSessionIntentVersion(n)},o=()=>this.isClaudeStartIntentCurrent(n,r),a=this.enqueueSessionLifecycle(async()=>{if(this.isStopping||!o()||this.activeSessions.get(e)!==t||this.claudeToBackendSessionId.get(t.claudeSessionId)!==e)return;let u={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 b=Ne(t.userId,t.claudeSessionId,e);if(await this.sessionBindingStore.write(t.userId,t.claudeSessionId,b),!o())return;let m=++this.lifecycleGeneration;await this.handleSessionEndSerialized({session_id:t.claudeSessionId,metadata:{reason:"backend-generation-retired"}}),!(!this.isLifecycleCurrent(m)||!o())&&await this.handleSessionStartSerialized(u,b,m,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 i of t)this.appSyncClient.stopHeartbeat(i),this.appSyncClient.cleanupSubscription(i);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(i){s.warn("Cancelled Claude bootstrap row could not be marked INACTIVE",{sessionId:e.authoritativeSessionId,error:i instanceof Error?i.message:String(i)})}}async emitBridgeUnavailableBanner(){let e=process.env[ie],t="[CodeVibe] Cross-device decision sync unavailable \u2014 mobile responses will be sent as free-text";if(s.warn(t),!!e)try{await Oe(`tmux display-message -t "${e}" -- ${JSON.stringify(t)}`)}catch(i){s.warn("[v1-bridge] tmux display-message for bridge-unavailable banner failed",{tmuxSession:e,error:String(i)})}}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,i=this.claudeToBackendSessionId.get(t)||this.generateBackendSessionId(t);s.info("Session ended",{claudeSessionId:t,sessionId:i,reason:e.metadata?.reason});let n=this.activeSessions.get(i);if(n?.mobileEndWatcher&&(n.mobileEndWatcher.stop(),n.mobileEndWatcher=void 0),n?.orchestrationPromptWatcher&&(n.orchestrationPromptWatcher.stop(),n.orchestrationPromptWatcher=void 0),n?.orchestrationDecisionWatcher&&(n.orchestrationDecisionWatcher.stop(),n.orchestrationDecisionWatcher=void 0),n?.orchestrationClient){try{await n.orchestrationClient.destroy()}catch(c){s.warn("[v1-bridge] OrchestrationClient.destroy() failed",{sessionId:i,error:String(c)})}n.orchestrationClient=void 0}this.toolActivity&&this.toolActivity.sessionId===i&&(await this.toolActivity.stop(),this.toolActivity=null),this.activeSessions.delete(i),this.claudeToBackendSessionId.delete(t),this.removePortFile(t),n?.waitingForPromptResponse&&(s.info("Clearing prompt wait state - session ending",{sessionId:i}),this.clearPromptState(n));let r=this.queuedMobilePromptTimers.get(i);r&&clearTimeout(r),this.queuedMobilePromptTimers.delete(i),this.queuedMobilePrompts.delete(i);let o=this.toolActivityLegacyRecorders.get(i);if(o&&o.disposeLegacyCoordination(),this.toolActivityLegacyRecorders.delete(i),this.appSyncClient.stopHeartbeat(i),this.appSyncClient.cleanupSubscription(i),n)try{await this.appSyncClient.updateSession({sessionId:i,status:p.SessionStatus.INACTIVE}),s.info("Session marked as INACTIVE in AppSync",{sessionId:i})}catch(c){s.warn("Failed to update session in AppSync:",c)}else s.warn("Cannot update session - session state not found",{sessionId:i});s.debug("Session cleanup completed",{sessionId:i})}async withMobilePromptAdmission(e,t){let i,r=(this.queuedMobilePromptAdmissions.get(e)??Promise.resolve()).catch(()=>{}).then(async()=>{i=await t()});this.queuedMobilePromptAdmissions.set(e,r);try{return await r,i}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 i=this.claudeToBackendSessionId.get(t)??e,n=this.activeSessions.get(i);if(!n||n.claudeSessionId!==t)throw new Error(`Cannot recover mobile queue for inactive native session ${t}`);let r=await this.mobilePromptQueueStore.read(t);for(let u of r)u.sessionId!==i&&(u.sessionId=i,await this.mobilePromptQueueStore.put(t,u)),u.state==="sending"&&(u.state="terminal",u.lastError="CodeVibe restarted while sending this message. It was not retried to avoid submitting it twice.",await this.mobilePromptQueueStore.put(t,u));let o=this.queuedMobilePrompts.get(i)??[],c=new Map;for(let u of[...r,...o])c.set(u.event.eventId,u);let a=[...c.values()];if(a.length>0){this.queuedMobilePrompts.set(i,a),n.processedEventIds||(n.processedEventIds=new Set);for(let u of a)n.processedEventIds.add(u.event.eventId);this.scheduleQueuedMobilePromptDrain(i)}}async enqueueMobilePrompt(e,t,i,n){let r=n??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,i))}async enqueueMobilePromptOnce(e,t,i,n){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===i.eventId);if(a){let d=a.sessionId!==r;return a.sessionId=r,n&&!a.pendingDismissal&&(a.pendingDismissal=n,d=!0),d&&await this.mobilePromptQueueStore.put(t,a),o&&(o.processedEventIds||(o.processedEventIds=new Set),o.processedEventIds.add(i.eventId),this.queuedMobilePrompts.set(r,c),n||this.scheduleQueuedMobilePromptDrain(r)),a}let u={version:2,nativeSessionId:t,sessionId:r,event:i,state:"queued",attempts:0,nextAttemptAt:Date.now(),pendingDismissal:n};return await this.mobilePromptQueueStore.put(t,u),c.push(u),o&&this.activeSessions.get(r)===o&&(this.queuedMobilePrompts.set(r,c),o.processedEventIds||(o.processedEventIds=new Set),o.processedEventIds.add(i.eventId),n||this.scheduleQueuedMobilePromptDrain(r)),u}scheduleQueuedMobilePromptDrain(e,t=0){let i=this.queuedMobilePromptTimers.get(e);i&&clearTimeout(i);let n=setTimeout(()=>{this.queuedMobilePromptTimers.delete(e),this.drainQueuedMobilePrompts(e)},Math.max(0,t));n.unref?.(),this.queuedMobilePromptTimers.set(e,n)}queuedPromptBackoffMs(e){return Math.min(l.MOBILE_PROMPT_QUEUE_MAX_BACKOFF_MS,500*2**Math.max(0,e-1))}async terminalizeQueuedMobilePrompt(e,t){try{if(!t.terminalNoticeSent){if(!await this.sendPromptError(e.sessionId,t.lastError??"The queued message could not be sent. Please retry it."))return s.warn("Queued mobile-prompt terminal notice dropped by the encryption chokepoint; retaining the record",{sessionId:e.sessionId,eventId:t.event.eventId}),!1;t.terminalNoticeSent=!0,await this.mobilePromptQueueStore.put(t.nativeSessionId,t)}return await this.acknowledgeQueuedMobilePromptExecuted(e,t)?(await this.mobilePromptQueueStore.remove(t.nativeSessionId,t.event.eventId),!0):!1}catch(i){return s.warn("Failed to publish queued mobile-prompt terminal disposition",{sessionId:e.sessionId,eventId:t.event.eventId,error:String(i)}),!1}}async acknowledgeQueuedMobilePromptExecuted(e,t){try{if(de("reject-executed-ack"))throw s.warn("DEV FAULT reject-executed-ack: simulating a failed EXECUTED acknowledgement",{eventId:t.event.eventId}),new Error("DEV FAULT: simulated EXECUTED acknowledgement failure");let i=await this.appSyncClient.updateEventStatus({eventId:t.event.eventId,sessionId:t.event.sessionId,timestamp:t.event.timestamp,deliveryStatus:p.DeliveryStatus.EXECUTED});return i?.eventId===t.event.eventId&&i?.sessionId===t.event.sessionId&&i?.timestamp===t.event.timestamp&&i?.deliveryStatus===p.DeliveryStatus.EXECUTED?(e.processedEventIds||(e.processedEventIds=new Set),e.processedEventIds.add(t.event.eventId),this.queuedAckRepairs.delete(t.event.eventId),this.queuedAckFailures.delete(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:i?.eventId,acknowledgedSessionId:i?.sessionId,acknowledgedTimestamp:i?.timestamp,acknowledgedDeliveryStatus:i?.deliveryStatus}),!1)}catch(i){let n=String(i);if(n.includes("INVALID_DELIVERY_STATUS_TRANSITION")&&!this.queuedAckRepairs.has(t.event.eventId)){s.warn("Queued mobile-prompt EXECUTED acknowledgement rejected; repairing SENT\u2192DELIVERED first",{sessionId:e.sessionId,eventId:t.event.eventId});try{return await this.writeEventStatus(t.event,p.DeliveryStatus.DELIVERED),this.queuedAckRepairs.add(t.event.eventId),this.acknowledgeQueuedMobilePromptExecuted(e,t)}catch(r){return s.warn("Queued mobile-prompt DELIVERED repair failed",{sessionId:e.sessionId,eventId:t.event.eventId,error:String(r)}),!1}}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:n}),!1}}async abandonQueuedAcknowledgementIfExhausted(e,t){if(t.state==="terminal"&&t.terminalNoticeSent!==!0)return!1;let i=(this.queuedAckFailures.get(t.event.eventId)??0)+1;if(this.queuedAckFailures.set(t.event.eventId,i),i<l.MOBILE_PROMPT_QUEUE_MAX_ATTEMPTS)return!1;s.error("Abandoning the queued mobile-prompt EXECUTED acknowledgement so later prompts can drain",{sessionId:e.sessionId,eventId:t.event.eventId,state:t.state,failures:i});try{await this.mobilePromptQueueStore.remove(t.nativeSessionId,t.event.eventId)}catch(n){s.warn("Failed to remove the abandoned queue record",{sessionId:e.sessionId,eventId:t.event.eventId,error:String(n)})}return this.queuedAckRepairs.delete(t.event.eventId),this.queuedAckFailures.delete(t.event.eventId),!0}async reconcileQueuedPromptDismissal(e,t){let i=t.pendingDismissal;if(!i)return"resolved";let n=i.invocation.occurrenceId,r=e.pendingAskUserQuestionInvocations?.some(c=>c.occurrenceId===n)??!1,o=e.activeAskUserQuestionInvocation?.occurrenceId;return!r&&o!==n?(i.dismissalSent&&await this.settleDismissedAskUserQuestionOccurrence(e,i.invocation),t.pendingDismissal=void 0,await this.mobilePromptQueueStore.put(t.nativeSessionId,t),"resolved"):o!==n||e.pendingPanePromptIdentity?.hookOccurrenceId!==n?(o&&o!==n&&await this.settleDismissedAskUserQuestionOccurrence(e,i.invocation),t.pendingDismissal=void 0,await this.mobilePromptQueueStore.put(t.nativeSessionId,t),"resolved"):i.nextAttemptAt>Date.now()?(this.scheduleQueuedMobilePromptDrain(e.sessionId,i.nextAttemptAt-Date.now()),"retry"):await this.resolveAskUserQuestionAfterVerifiedDismissal(e,i.expectedIdentity,i.invocation,{attempts:1,delayMs:0})?(t.pendingDismissal=void 0,await this.mobilePromptQueueStore.put(t.nativeSessionId,t),"resolved"):(i.attempts+=1,i.attempts>=l.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"):(i.nextAttemptAt=Date.now()+this.queuedPromptBackoffMs(i.attempts),t.lastError="Waiting for the dismissed desktop chooser to close",await this.mobilePromptQueueStore.put(t.nativeSessionId,t),this.scheduleQueuedMobilePromptDrain(e.sessionId,i.nextAttemptAt-Date.now()),"retry"))}async drainQueuedMobilePrompts(e){if(this.queuedMobilePromptDrains.has(e))return;let t=this.activeSessions.get(e),i=this.queuedMobilePrompts.get(e);if(!t||!i?.length)return;this.queuedMobilePromptDrains.add(e);let n=new Set;try{for(;i.length>0;){if(this.activeSessions.get(e)!==t)return;let r=i[0];if(r.state==="submitted"){if(!await this.acknowledgeQueuedMobilePromptExecuted(t,r)){if(await this.abandonQueuedAcknowledgementIfExhausted(t,r)){i.shift();continue}this.scheduleQueuedMobilePromptDrain(e,2e3);return}await this.mobilePromptQueueStore.remove(r.nativeSessionId,r.event.eventId),i.shift();continue}if(r.state==="terminal"){if(!await this.terminalizeQueuedMobilePrompt(t,r)){if(await this.abandonQueuedAcknowledgementIfExhausted(t,r)){i.shift();continue}if(r.terminalNoticeSent!==!0&&!n.has(r.event.eventId)){n.add(r.event.eventId),i.push(i.shift());continue}this.scheduleQueuedMobilePromptDrain(e,2e3);return}i.shift();continue}let o=await this.reconcileQueuedPromptDismissal(t,r);if(o==="retry")return;if(o==="terminal")continue;if(t.activeAskUserQuestionInvocation||t.waitingForPromptResponse||t.promptGenerationToken!==void 0){this.scheduleQueuedMobilePromptDrain(e,500);return}let c=r.nextAttemptAt-Date.now();if(c>0){this.scheduleQueuedMobilePromptDrain(e,c);return}if(!await this.waitForChooserAbsence(e,t,{attempts:1,delayMs:0})){r.attempts+=1,r.attempts>=l.MOBILE_PROMPT_QUEUE_MAX_ATTEMPTS?(r.state="terminal",r.lastError="The desktop chooser did not close, so this message was not submitted. Please retry it."):(r.nextAttemptAt=Date.now()+this.queuedPromptBackoffMs(r.attempts),r.lastError="Waiting for the desktop chooser to close"),await this.mobilePromptQueueStore.put(r.nativeSessionId,r);continue}if(de("force-terminal")){s.warn("DEV FAULT force-terminal: marking the queued prompt terminal instead of sending it",{eventId:r.event.eventId}),r.state="terminal",r.lastError="The desktop chooser did not close, so this message was not submitted. Please retry it.",await this.mobilePromptQueueStore.put(r.nativeSessionId,r);continue}if(r.state="sending",r.lastError=void 0,await this.mobilePromptQueueStore.put(r.nativeSessionId,r),await this.executeMobilePrompt(e,r.event,{suppressFailureNotification:!0,deferStatusUpdate:!0})){r.state="submitted",await this.mobilePromptQueueStore.put(r.nativeSessionId,r);continue}r.state="terminal",r.lastError="CodeVibe could not prove whether this message reached Claude. It was not retried to avoid submitting it twice.",await this.mobilePromptQueueStore.put(r.nativeSessionId,r)}this.queuedMobilePrompts.delete(e)}finally{this.queuedMobilePromptDrains.delete(e),i.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 i=>this.dispatchMobileEvent(e,i),i=>{s.error("Subscription error",{sessionId:e,error:i})},{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 i=t.content||"";if(t.isEncrypted&&this.sessionKey)try{i=p.cryptoService.decryptContent(t.content,this.sessionKey),s.debug("Event decrypted successfully",{eventId:t.eventId})}catch(f){s.error("Failed to decrypt event:",{eventId:t.eventId,error:f}),i=t.content}let n,r,o=!1,c,a,u,d,b;if(t.type===p.EventType.USER_PROMPT||t.type===p.EventType.PROMPT_RESPONSE)if(r=this.activeSessions.get(e),b=(0,p.classifyMobilePromptInput)(i,Object.keys(r?.pendingSubmitMap??{})),!r)n="no-session";else if(r.processedEventIds?.has(t.eventId))n="skip-dedup";else if(r.inFlightEventIds?.has(t.eventId))n="drop-event-redeliver";else if(r.waitingForPromptResponse){let f=r.promptGenerationToken;if(!f)n="regular";else if(t.type===p.EventType.USER_PROMPT&&b.kind==="option"&&r.hasReceivedPromptResponse&&(!t.promptId||t.promptId.length===0))n="drop-stale-answer";else if(b.kind==="option"&&t.type===p.EventType.PROMPT_RESPONSE&&(!t.promptId||f.promptId.length===0||t.promptId!==f.promptId))n="drop-stale-answer";else if(b.kind==="option"&&t.promptId&&f.promptId.length>0&&t.promptId!==f.promptId)n="drop-stale-answer";else{let g=f.promptId.length>0?f.promptId:`__prompt_gen_${f.gen}`;r.inFlightPromptIds?.has(g)?n="drop-in-flight":(r.inFlightPromptIds||(r.inFlightPromptIds=new Set),r.inFlightEventIds||(r.inFlightEventIds=new Set),r.inFlightPromptIds.add(g),r.inFlightEventIds.add(t.eventId),o=!0,u=g,d=t.eventId,c={promptId:f.promptId,gen:f.gen},a={pendingPromptId:r.pendingPromptId,pendingSubmitMap:r.pendingSubmitMap?{...r.pendingSubmitMap}:void 0,pendingPanePromptIdentity:r.pendingPanePromptIdentity,activeAskUserQuestionInvocation:r.activeAskUserQuestionInvocation},n="walker")}}else n="regular";else n="not-user-prompt";let w={...t,content:i};if(n!=="skip-dedup")if(de("skip-delivered"))s.warn("DEV FAULT skip-delivered: leaving the event SENT (no DELIVERED mark)",{eventId:t.eventId});else 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(f){s.warn("Failed to mark event as DELIVERED",{eventId:t.eventId,error:f})}if(t.type===p.EventType.USER_PROMPT&&r?.pendingOrchestrationGate&&await this.handleOrchestrationPromptResponse(r,i,t)){u&&r.inFlightPromptIds?.delete(u),d&&r.inFlightEventIds?.delete(d);return}if(n==="skip-dedup"){s.info("[walker] Subscription-level dedup hit (already processed) \u2014 skipping",{sessionId:e,eventId:t.eventId});return}if(n==="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(f){s.warn("[walker] markEventExecuted threw on stale-answer drop \u2014 relying on processedEventIds Set",{sessionId:e,eventId:t.eventId,error:String(f)})}return}if(n==="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(f){s.warn("[walker] markEventExecuted threw on subscription-level duplicate drop \u2014 relying on processedEventIds Set",{sessionId:e,eventId:t.eventId,error:String(f)})}return}if(n==="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(n==="walker"){t.type===p.EventType.PROMPT_RESPONSE&&b?.kind==="option"&&r&&(r.hasReceivedPromptResponse=!0),await this.handleMobilePromptResponse(e,t,i,r,o,c,u,d,a);return}if(n==="regular"){if(t.type===p.EventType.PROMPT_RESPONSE&&b?.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(f){s.warn("markEventExecuted threw on PROMPT_RESPONSE orphan drop \u2014 relying on processedEventIds Set",{sessionId:e,eventId:t.eventId,error:String(f)})}return}await this.executeMobilePrompt(e,w);return}if(n==="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,i,n,r=!1,o,c,a,u){let d=o??n.promptGenerationToken,b=c,m=a;if(!r&&d){let w=d.promptId.length>0?d.promptId:`__prompt_gen_${d.gen}`;if(n.inFlightPromptIds?.has(w)){s.warn("[walker] Duplicate mobile USER_PROMPT for same prompt \u2014 dropping",{sessionId:e,eventId:t.eventId,lockKey:w}),await this.markEventExecutedIdempotent(n,t);return}n.inFlightPromptIds||(n.inFlightPromptIds=new Set),n.inFlightEventIds||(n.inFlightEventIds=new Set),n.inFlightPromptIds.add(w),n.inFlightEventIds.add(t.eventId),b=w,m=t.eventId}try{if(!r&&n.processedEventIds?.has(t.eventId)){s.info("[walker] Redelivered event already processed \u2014 skipping",{sessionId:e,eventId:t.eventId});return}let w=i.trim(),f=u?u.pendingPromptId:n.pendingPromptId,g=u?u.pendingSubmitMap:n.pendingSubmitMap,y=u?u.pendingPanePromptIdentity:n.pendingPanePromptIdentity,h=u?u.activeAskUserQuestionInvocation:n.activeAskUserQuestionInvocation,v=g,I=this.parseInteractivePromptInput(w,Object.keys(v??{}));if(s.info("Parsed interactive prompt input",{sessionId:e,content:w,parsed:I,hasSubmitMap:!!v}),I.action==="send_as_response"&&y?.kind==="ask_user_question"){let F=n.promptGenerationToken,U=this.activeSessions.get(e)===n&&F?.gen===d?.gen&&F?.promptId===d?.promptId,T=h,R=U&&T?{expectedIdentity:y,invocation:T,attempts:0,nextAttemptAt:Date.now()}:void 0,A=await this.enqueueMobilePrompt(e,{...t,content:i},R,n.claudeSessionId);if(!U||!T){this.scheduleQueuedMobilePromptDrain(e);return}let O=await this.dismissLivePromptForConversation(n,d);O&&A.pendingDismissal&&(A.pendingDismissal.dismissalSent=!0,await this.mobilePromptQueueStore.put(A.nativeSessionId,A));let Q=O&&T&&await this.resolveAskUserQuestionAfterVerifiedDismissal(n,y,T);Q&&A.pendingDismissal&&(A.pendingDismissal=void 0,await this.mobilePromptQueueStore.put(A.nativeSessionId,A)),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,n,S))return!1;let F=n.promptGenerationToken,U=F?.gen,T=d?.gen;return this.activeSessions.get(e)!==n||U!==T?(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:F}),!0):!1};if(E()){await this.markEventExecutedIdempotent(n,t);return}if(I.action==="select_option"){let F=v?.[I.option]||I.option,U=n.pendingQuestionsQueue!==void 0,T=n.pendingCurrentQuestion?.options?.length??0,R=I.option?parseInt(I.option,10):NaN,A=n.pendingSynthTail?.length??0;if(U&&n.pendingCurrentQuestion!==void 0&&A>0&&Number.isFinite(R)&&R>T&&R<=T+A){if(await this.markEventExecutedIdempotent(n,t),E())return;let D=await this.sendVerifiedPromptKey(n,y,"Escape");if(E())return;if(!(D&&y?.kind==="ask_user_question"&&h&&await this.resolveAskUserQuestionAfterVerifiedDismissal(n,y,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:I.option,terminalInput:F,isV2AskUserQuestion:U});let Q=parseInt(I.option,10)-1,C=n.pendingCurrentQuestion?.options?.[Q],_=typeof C=="string"?C:C&&typeof C=="object"?C.label:`option ${I.option}`,V=n.pendingCurrentQuestion?._isSubmit===!0,L=await this.answerVerifiedInteractivePrompt(n,y,F,{pressEnter:!U});if(E()){await this.markEventExecutedIdempotent(n,t);return}if(L){if(await this.markEventExecutedIdempotent(n,t),E())return;if(!f){s.warn("emitAnswerAck called without promptId \u2014 clearing state + skipping ack",{sessionId:e,source:"select_option",eventId:t.eventId}),this.promoteFingerprintAndClearPromptState(n);return}let D=(n.pendingQuestionsQueue?.length??0)===0;try{if(U){let Y=_,ue=V,oe=Y.toLowerCase(),fe;ue&&oe==="cancel"?fe="\u2192 Cancel \u2014 AskUserQuestion cancelled, no answers submitted":ue&&oe.startsWith("submit")?fe="\u2192 Submit answers \u2014 AskUserQuestion completed":fe=`\u2192 ${Y}`,await this.emitUserChoice(e,fe)}else await this.emitAnswerAck(e,`Selected option ${I.option}`,{promptId:f,questionIndex:0,isTerminal:D})}catch(Y){s.warn("[walker] user-choice/ack emit failed \u2014 continuing to STEP 7/8",{sessionId:e,promptId:f,isV2AskUserQuestion:U,error:Y instanceof Error?Y.message:String(Y)})}if(E())return;let $=n.pendingQuestionsQueue?.shift();if($&&(n.pendingCurrentQuestion=$,n.pendingPanePromptIdentity=this.buildAskUserQuestionPaneIdentity($,n.activeAskUserQuestionInvocation?.occurrenceId)),!$){n.activeAskUserQuestionFingerprint&&(n.completedAskUserQuestionFingerprints||(n.completedAskUserQuestionFingerprints=new Set),n.completedAskUserQuestionFingerprints.add(n.activeAskUserQuestionFingerprint),s.info("AskUserQuestion V2 walker complete \u2014 fingerprint marked closed",{sessionId:e,fingerprint:n.activeAskUserQuestionFingerprint.slice(0,16)})),this.clearPromptState(n);return}let ee=this.newE1Raise(e,!0),N=ee.promptId;if(n.activeAskUserQuestionInvocation?.e1PromptIds.push(N),!N){this.promoteFingerprintAndClearPromptState(n),s.warn("Q[next] emit aborted: synthesized promptId was empty; promoted fingerprint + cleared prompt state",{sessionId:e,eventId:t.eventId});return}let te=n.pendingSynthTail??[],Z=this.buildQuestionWireData($,te),M=$.question,k={tool_name:"AskUserQuestion",tool_input:{questions:[$]},options:Z.options,submitMap:Z.submitMap,instructions:Z.instructions},P=n.encryptionSnapshot,K=(0,p.encryptForEmit)(P?.sessionKey??null,P?.sessionIsEncrypted??!1,M,k,P?P.encryptionResolved:!1);if(!K){s.error("No session key for ENCRYPTED session \u2014 dropping Q[next] prompt (fail-closed, #638)",{sessionId:e,promptId:N}),this.promoteFingerprintAndClearPromptState(n);return}let j=K.content,re=K.metadata,se=K.isEncrypted,ne=this.nextPromptGen++,ce={promptId:N,gen:ne};n.pendingPromptId=N,n.pendingSubmitMap=Z.submitMap,n.promptGenerationToken=ce;let X=ce,he=this.activeSessions.get(e)?.promptGenerationToken;if(!he||he.gen!==X.gen||he.promptId!==X.promptId){s.warn("Q[next] emit aborted: token replaced before await dispatch",{sessionId:e,tokenAtAwait:X,currentToken:he});return}let He=N.length>0?N:`__prompt_gen_${ce.gen}`;n.inFlightPromptIds||(n.inFlightPromptIds=new Set),n.inFlightPromptIds.add(He);try{this.stashE1RaiseContent(N,M,k);try{await this.promptPublications.run(N,()=>this.appSyncClient.createEvent({sessionId:e,type:p.EventType.INTERACTIVE_PROMPT,source:p.EventSource.DESKTOP,content:j,metadata:re,...(0,p.raiseFieldsFromRecord)(ee),timestamp:(0,p.prepareEventTimestamp)({orderingKey:e}),isEncrypted:se?!0:void 0})),s.info("Q[next] emit succeeded",{sessionId:e,promptId:N,remaining:n.pendingQuestionsQueue?.length??0})}catch(Y){let ue=this.activeSessions.get(e),oe=ue?.promptGenerationToken;oe&&oe.gen===X.gen&&oe.promptId===X.promptId?(this.promoteFingerprintAndClearPromptState(ue),s.warn("Q[next] emit failed; promoted fingerprint + cleared prompt state. User must answer remaining questions on desktop terminal.",{sessionId:e,promptId:N,error:Y instanceof Error?Y.message:String(Y)})):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:X,currentToken:oe,error:Y instanceof Error?Y.message:String(Y)})}}finally{n.inFlightPromptIds.delete(He)}}else await this.publishRefusalThenMark(n,t,this.describeAskUserQuestionRefusal(n))}else if(I.action==="option_with_followup"){if(n.pendingQuestionsQueue!==void 0){let R=this.getWalkerPosition(n),A=n.pendingCurrentQuestion?.options?.length??0,O=I.option?parseInt(I.option,10):NaN,Q=Number.isFinite(O)&&O>A,C=I.followUpText??"";if(s.info("AUQ walker \u2014 option_with_followup dispatch",{sessionId:e,option:I.option,optionNum:O,explicitCount:A,isSynthDigit:Q,walkerPosition:R,followUpTextLen:C.length}),await this.markEventExecutedIdempotent(n,t),E())return;if(Q){if(R==="on_synth"){let M=await this.answerVerifiedInteractivePrompt(n,y,"2",{pressEnter:!1});if(E())return;if(!M){try{await this.emitUserChoice(e,`AskUserQuestion was not cancelled \u2014 ${this.describeAskUserQuestionRefusal(n).replace(/\.?$/,".")} Your reply was not sent.`)}catch(k){s.warn("emitUserChoice on synth-digit on-SYNTH Cancel failed",{sessionId:e,error:k instanceof Error?k.message:String(k)})}return}if(await new Promise(k=>setTimeout(k,1500)),E())return;if(C){let k=await this.sendFreeFormWhenNoChooser(e,n,C,S);if(E())return;k||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(k){s.warn("emitUserChoice on synth-digit on-SYNTH success failed",{sessionId:e,error:k instanceof Error?k.message:String(k)})}if(E())return;this.promoteFingerprintAndClearPromptState(n);return}await this.handleMobileReplyAsDismissAndPrompt(e,n,y,C,S,E);return}let _=v?.[I.option]||I.option,V=O-1,L=n.pendingCurrentQuestion?.options?.[V],D=typeof L=="string"?L:L&&typeof L=="object"?L.label:`option ${I.option}`,$=n.pendingCurrentQuestion?._isSubmit===!0;if(R==="q1"){let M=await this.answerVerifiedInteractivePrompt(n,y,_,{pressEnter:!1});if(E())return;if(!M){try{await this.publishNotificationWithRetries(e,`${this.describeAskUserQuestionRefusal(n).replace(/\.?$/,".")} Your reply (including the follow-up text) was not sent.`,{eventId:t.eventId,purpose:"refusal-followup-q1"})}catch(k){s.warn("sendPromptError on q1 option_with_followup failed",{sessionId:e,error:k instanceof Error?k.message:String(k)})}return}if(await new Promise(k=>setTimeout(k,1500)),E())return;if(C){let k=await this.sendFreeFormWhenNoChooser(e,n,C,S);if(E())return;k||s.warn("q1 option_with_followup followup text-send failed",{sessionId:e})}try{await this.emitUserChoice(e,C?`\u2192 ${D} + sending your reply as a new prompt`:`\u2192 ${D}`)}catch(k){s.warn("emitUserChoice on q1 option_with_followup success failed",{sessionId:e,error:k instanceof Error?k.message:String(k)})}if(E())return;this.promoteFingerprintAndClearPromptState(n);return}if(R==="nq_mid"){let M=await this.answerVerifiedInteractivePrompt(n,y,_,{pressEnter:!1});if(E())return;if(!M){try{await this.sendPromptError(e,`${this.describeAskUserQuestionRefusal(n).replace(/\.?$/,".")} Your reply (including the follow-up text) was not sent.`)}catch(P){s.warn("sendPromptError on nq_mid option_with_followup failed",{sessionId:e,error:P instanceof Error?P.message:String(P)})}return}if(await new Promise(P=>setTimeout(P,200)),E())return;let k=n.pendingQuestionsQueue?.shift();k?(n.pendingCurrentQuestion=k,n.pendingPanePromptIdentity=this.buildAskUserQuestionPaneIdentity(k,n.activeAskUserQuestionInvocation?.occurrenceId)):s.warn("nq_mid option_with_followup: queue.shift returned undefined unexpectedly",{sessionId:e}),await this.handleMobileReplyAsDismissAndPrompt(e,n,n.pendingPanePromptIdentity,C,S,E);return}if(R==="nq_last"){await this.handleOptionWithFollowupOnLastQ(e,n,y,_,D,C,S,E);return}let ee=await this.answerVerifiedInteractivePrompt(n,y,_,{pressEnter:!1});if(E())return;if(!ee){try{await this.sendPromptError(e,`${this.describeAskUserQuestionRefusal(n).replace(/\.?$/,".")} Your reply was not sent.`)}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(C){let M=await this.sendFreeFormWhenNoChooser(e,n,C,S);if(E())return;M||s.warn("on-SYNTH option_with_followup text-send failed",{sessionId:e})}let N=$,te=D.toLowerCase(),Z;N&&te==="cancel"?Z=C?"\u2192 Cancel \u2014 AskUserQuestion cancelled, sending your reply as a new prompt":"\u2192 Cancel \u2014 AskUserQuestion cancelled, no answers submitted":N&&te.startsWith("submit")?Z=C?"\u2192 Submit answers \u2014 AskUserQuestion completed, sending your reply as a new prompt":"\u2192 Submit answers \u2014 AskUserQuestion completed":Z=C?`\u2192 ${D} + sending your reply as a new prompt`:`\u2192 ${D}`;try{await this.emitUserChoice(e,Z)}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(n);return}let U=v?.[I.option]||I.option;s.info("User selected option with follow-up",{option:I.option,terminalInput:U,followUpText:I.followUpText});let T=await this.answerVerifiedInteractivePrompt(n,y,U);if(E()){await this.markEventExecutedIdempotent(n,t);return}if(T){if(await this.markEventExecutedIdempotent(n,t),E())return;if(!f){s.warn("emitAnswerAck called without promptId \u2014 clearing state + skipping ack",{sessionId:e,source:"option_with_followup",eventId:t.eventId}),this.promoteFingerprintAndClearPromptState(n);return}try{await this.emitAnswerAck(e,`Selected option ${I.option}`,{promptId:f,questionIndex:0,isTerminal:!0})}catch(R){s.warn("[walker] emitAnswerAck (option_with_followup) failed \u2014 continuing to clearPromptState + executeMobilePrompt",{sessionId:e,promptId:f,error:R instanceof Error?R.message:String(R)})}if(E())return;if(this.promoteFingerprintAndClearPromptState(n),I.followUpText){await new Promise(A=>setTimeout(A,1e3));let R={...t,content:I.followUpText};await this.executeMobilePrompt(e,R)}}else try{await this.sendPromptError(e,`${this.describeAskUserQuestionRefusal(n).replace(/\.?$/,".")} Your reply (including the follow-up text) was not sent.`)}catch(R){s.warn("[walker] sendPromptError threw \u2014 relying on idempotent mark in finally",{sessionId:e,eventId:t.eventId,error:String(R)})}finally{await this.markEventExecutedIdempotent(n,t)}}else{if(n.pendingQuestionsQueue!==void 0){let T=this.getWalkerPosition(n);if(s.info("AUQ walker \u2014 send_as_response dispatch",{sessionId:e,walkerPosition:T,contentPreview:w.slice(0,80)}),await this.markEventExecutedIdempotent(n,t),E())return;if(T==="on_synth"){let R=await this.answerVerifiedInteractivePrompt(n,y,"2",{pressEnter:!1});if(E())return;if(!R){try{await this.emitUserChoice(e,`AskUserQuestion was not cancelled \u2014 ${this.describeAskUserQuestionRefusal(n).replace(/\.?$/,".")} Your reply was not sent.`)}catch(O){s.warn("emitUserChoice on send_as_response on-SYNTH Cancel failed",{sessionId:e,error:O instanceof Error?O.message:String(O)})}return}if(await new Promise(O=>setTimeout(O,1500)),E())return;let A=await this.sendFreeFormWhenNoChooser(e,n,w,S);if(E())return;A||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(O){s.warn("emitUserChoice on send_as_response on-SYNTH success failed",{sessionId:e,error:O instanceof Error?O.message:String(O)})}if(E())return;this.promoteFingerprintAndClearPromptState(n);return}await this.handleMobileReplyAsDismissAndPrompt(e,n,y,w,S,E);return}s.info("Rejecting legacy prompt before forwarding free-form mobile input",{responsePreview:w.slice(0,80),attachmentCount:t.attachments?.length??0});let U=await this.dismissLivePromptForConversation(n,d);if(E()){await this.markEventExecutedIdempotent(n,t);return}if(U)await this.clearInteractivePromptsForAcceptedInput(n),await new Promise(T=>setTimeout(T,200)),await this.executeMobilePrompt(e,{...t,content:w});else try{await this.sendPromptError(e,"Failed to dismiss the active prompt; your message was not sent")}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(n,t)}}}finally{b&&n.inFlightPromptIds&&n.inFlightPromptIds.delete(b),m&&n.inFlightEventIds&&n.inFlightEventIds.delete(m)}}async sendInteractivePromptAsync(e,t,i){let n=this.activeSessions.get(e),r=n?.activeAskUserQuestionInvocation?.occurrenceId,o=n?.promptGenerationToken?{...n.promptGenerationToken}:void 0,c=(0,p.prepareEventTimestamp)({orderingKey:e});await new Promise(T=>setTimeout(T,500));let a=process.env.CODEVIBE_TMUX_SESSION,u={...t.metadata||{}},d,b=t.metadata?.tool_name,m=t.metadata?.tool_input,w=b==="AskUserQuestion"&&Array.isArray(m?.questions)?m.questions:[];if(w.length>0&&Array.isArray(w[0]?.options)&&w[0].options.length>0){let T=w[0];d=this.buildAskUserQuestionPaneIdentity(T,r);let R=[];if(a)try{let{exec:_}=await import("child_process"),V=D=>new Promise(($,ee)=>{_(D,{timeout:5e3},(N,te)=>{N?ee(N):$({stdout:te||""})})}),{stdout:L}=await V(`tmux capture-pane -p -e -S -30 -t '${a}'`);R=this.parseAskUserQuestionSynthTail(L),s.info("AskUserQuestion synth-tail parsed from tmux",{tailCount:R.length,tail:R.map(D=>D.label)})}catch(_){s.warn("Failed to capture tmux for AskUserQuestion synth-tail; emitting without synth tail",{error:_ instanceof Error?_.message:String(_)})}else s.info("No tmux session \u2014 AskUserQuestion synth tail will be empty");let A=this.activeSessions.get(e);if(A){let _=A.promptGenerationToken;o&&_?.gen===o.gen?A.pendingSynthTail=R:s.warn("AskUserQuestion synth-tail: stale async \u2014 token gen mismatch, skipping pendingSynthTail write",{tokenAtEmit:o,currentToken:_,sessionId:e})}let O=this.buildQuestionWireData(T,R);u.options=JSON.parse(JSON.stringify(O.options)),u.submitMap=JSON.parse(JSON.stringify(O.submitMap)),u.instructions=O.instructions,u.tool_name="AskUserQuestion",u.tool_input={questions:[T]},i=T.question;let Q=typeof t.prompt_id=="string"&&t.prompt_id.length>0,C=fn(w);if(Q){let _=this.activeSessions.get(e);if(_){let V=_.promptGenerationToken;o&&V?.gen===o.gen?(_.pendingQuestionsQueue=C.remainingQueue,_.pendingCurrentQuestion=T,_.pendingPanePromptIdentity=d,_.hasReviewScreen=C.hasReviewScreen):s.warn("AskUserQuestion: stale async \u2014 token gen mismatch, skipping walker-state write",{tokenAtEmit:o,currentToken:V,sessionId:e})}}else s.warn("AskUserQuestion: empty prompt_id, degrading to single-Q legacy emit",{questionCount:w.length});s.info("AskUserQuestion: emitting Q1 only (Q2..QN queued)",{questionCount:C.questionCount,hasReviewScreen:C.hasReviewScreen,queuedRemaining:Q?C.remainingQueue.length:0,optionCountFirst:O.options.length,questionPreview:T.question.slice(0,80)})}else if(a)try{let{exec:T}=await import("child_process"),R=C=>new Promise((_,V)=>{T(C,{timeout:5e3},(L,D)=>{L?V(L):_({stdout:D||""})})}),{stdout:A}=await R(`tmux capture-pane -p -e -S -30 -t '${a}'`),O=A.split(`
6
+ `)){let r=n.trim();if(!r)continue;let o=r.indexOf(" ");if(o<0)continue;let c=parseInt(r.substring(0,o),10);if(isNaN(c)||c===i)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{s.info("Starting CodeVibe MCP Server...",{environment:(0,p.getEnvironment)()}),this.appSyncClient=new p.AppSyncClient;let e=Me();if(e.size>0&&s.warn("DEV FAULTS ACTIVE (development only) \u2014 failure paths will be exercised on purpose",{faults:Object.fromEntries(e)}),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(i=>{s.warn("Failed to update available agents (non-fatal)",{error:i?.message})});try{let i=await this.appSyncClient.sweepOrphanSessions({agentType:"CLAUDE"});i>0&&s.info("Orphan sweep: marked stale Claude sessions INACTIVE",{swept:i})}catch(i){s.warn("Orphan sweep failed, continuing startup",{error:i instanceof Error?i.message:String(i)})}}else{let i=(0,p.describeStoredAuthFailure)(this.appSyncClient.getLastAuthFailureKind(),this.appSyncClient.getLastAuthFailureDetail());s.error("Authentication with stored credentials failed",{kind:this.appSyncClient.getLastAuthFailureKind(),exitCode:i.exitCode}),console.error(i.message),process.exit(i.exitCode)}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(cn,Math.max(an,e)):on;this.portRefreshTimer=setInterval(()=>this.refreshPortFiles(),t),this.e1TtlRefreshTimer&&clearInterval(this.e1TtlRefreshTimer),this.e1TtlRefreshTimer=setInterval(()=>{this.refreshE1Ttls()},l.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 i of t){let n=this.e1PromptRaises.get(i);n&&this.activeSessions.has(n.record.sessionId)&&await this.reRaiseOneE1(n.record.sessionId,n)}}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 i of this.activeSessions.values()){let n=H.join(J.tmpdir(),`codevibe-claude-${i.claudeSessionId}.port`);try{W.utimesSync(n,e,e)}catch(o){o?.code==="ENOENT"?this.writePortFile(i.claudeSessionId):s.warn(`Port-file refresh failed: ${n}`,o)}let r=H.join(J.tmpdir(),`codevibe-claude-${i.claudeSessionId}.pid`);try{W.utimesSync(r,e,e)}catch(o){o?.code!=="ENOENT"&&s.warn(`Pid-file refresh failed: ${r}`,o)}}let t=process.env[ie];if(t){let i=H.join(J.tmpdir(),`codevibe-claude-instance-${t}.json`);try{W.utimesSync(i,e,e)}catch(n){n?.code!=="ENOENT"&&s.warn(`Instance-map refresh failed: ${i}`,n)}}}startTmuxLifecycleMonitor(){if(this.tmuxLifecycleTimer)return;let e=process.env[ie];e&&(this.tmuxLifecycleInitDeadlineMs=Date.now()+rn,this.tmuxLifecycleTimer=setInterval(()=>{this.checkTmuxLifecycle(e)},nn),this.tmuxLifecycleTimer.unref?.(),this.checkTmuxLifecycle(e))}async tmuxSessionState(e){try{return await en("tmux",["has-session","-t",e]),"alive"}catch(t){let i=kt(t);return i==="absent"?i:(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 i=this.tmuxLifecycleObservedAlive||Date.now()>=this.tmuxLifecycleInitDeadlineMs;if(t==="unknown"||!i||this.isStopping)return;this.stopTmuxLifecycleMonitor(),s.info("Native Claude tmux session ended; stopping companion daemon",{tmuxSession:e});let n=Be(r=>process.exit(r),()=>s.error("Shutdown watchdog fired after native session end; exiting anyway",{watchdogMs:25e3,timedOutSteps:this.teardownStepsTimedOut,durableObligationSettled:this.isDurableObligationSettled(),unretired:Array.from(this.pendingRetirements)}),25e3,()=>this.isDurableObligationSettled());this.stop().then(()=>{n(),process.exit(0)},r=>{n(),s.error("Failed to stop companion daemon after native session end",{error:r instanceof Error?r.message:String(r)}),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[ie];if(!t)return!0;let i=await this.tmuxSessionState(t);return i==="absent"?!0:(s.info("Ignoring SIGHUP while native Claude tmux session remains available",{tmuxSession:t,state:i}),!1)}stop(){return this.stopPromise?this.stopPromise:(this.stopPromise=this.doStop(),this.stopPromise)}async doStop(){if(s.info("Stopping MCP Server..."),this.isStopping=!0,this.stopTmuxLifecycleMonitor(),this.lifecycleGeneration+=1,this.clearEncryptedKeyRecovery("daemon stopping"),this.teardownStepsTimedOut=[],this.retirementPhaseAbandoned=!1,await this.boundedTeardownStep("sessionStartChain.join",()=>this.sessionStartChain.catch(()=>{})),this.portRefreshTimer&&(clearInterval(this.portRefreshTimer),this.portRefreshTimer=null),this.e1TtlRefreshTimer&&(clearInterval(this.e1TtlRefreshTimer),this.e1TtlRefreshTimer=null),this.toolActivity){let c=this.toolActivity,a,u=!1,d=await Ae(async()=>{try{await c.stop()}catch(b){u=!0,a=b}},3e3,()=>s.warn("toolActivity.stop timed out \u2014 continuing teardown without it",{timeoutMs:3e3}));if(d==="timeout"&&this.teardownStepsTimedOut.push("toolActivity.stop"),u)throw a;d==="settled"&&(this.toolActivity=null)}let e=Array.from(this.activeSessions.keys()),t=new Set,i=[];s.info(`Marking ${e.length} active session(s) as INACTIVE...`);for(let c of e){let a=this.activeSessions.get(c);a?.mobileEndWatcher&&(a.mobileEndWatcher.stop(),a.mobileEndWatcher=void 0),this.appSyncClient.stopHeartbeat(c)}this.pendingRetirements=new Set(e),await Ae(async()=>{for(let c of e){if(this.retirementPhaseAbandoned)return;let a=this.activeSessions.get(c);try{if(a&&this.hasOtherLiveDaemonForSession(a.claudeSessionId)){s.info("Another daemon serves this session \u2014 skipping mark INACTIVE AND port file removal during shutdown",{sessionId:c,claudeSessionId:a.claudeSessionId,myPid:process.pid}),t.add(a.claudeSessionId),this.pendingRetirements.delete(c);continue}try{de("stall-retirement")&&(s.warn("[dev-fault] stall-retirement: this INACTIVE write will never settle",{sessionId:c}),await new Promise(()=>{})),await this.retireHostedSession(c),this.pendingRetirements.delete(c),s.info("Session marked as INACTIVE during shutdown",{sessionId:c})}catch(u){i.push(c),s.error("Failed to mark session as INACTIVE during shutdown",{sessionId:c,error:u})}a&&this.removeOwnedSessionFiles(a.claudeSessionId)}catch(u){s.warn("Failed during shutdown cleanup",{sessionId:c,error:u})}}},8e3,()=>{this.retirementPhaseAbandoned=!0,s.warn("session retirement phase timed out \u2014 continuing teardown",{timeoutMs:8e3,unretired:Array.from(this.pendingRetirements)})})==="timeout"&&this.teardownStepsTimedOut.push("retirementPhase"),this.appSyncClient.cleanupSubscriptions(),this.activeSessions.clear(),await this.boundedTeardownStep("httpApi.stop",()=>this.httpApi.stop({protectedSessionIds:t}));let r=process.env[ie];r&&t.size===0&&this.removeOwnedTmuxFiles(r),s.info("MCP Server stopped");let o=Array.from(this.pendingRetirements);if(i.length>0||o.length>0)throw new Error(`Failed to retire hosted Claude sessions: ${(i.length>0?i:o).join(", ")}${this.retirementPhaseAbandoned?" (retirement phase timed out)":""}`)}async boundedTeardownStep(e,t){await Ae(async()=>{try{await t()}catch(n){s.warn(`${e} failed`,{error:String(n)})}},3e3,()=>s.warn(`${e} timed out \u2014 continuing teardown without it`,{timeoutMs:3e3}))==="timeout"&&this.teardownStepsTimedOut.push(e)}isDurableObligationSettled(){return this.pendingRetirements.size===0}isDurableObligationSettledForExit(){return this.isDurableObligationSettled()}getTeardownStepsTimedOut(){return[...this.teardownStepsTimedOut]}getPendingRetirements(){return Array.from(this.pendingRetirements)}async retireHostedSession(e){let t;for(let i=1;i<=Re;i+=1)try{await this.appSyncClient.updateSession({sessionId:e,status:p.SessionStatus.INACTIVE});return}catch(n){t=n,s.warn("Failed to retire hosted Claude session",{sessionId:e,attempt:i,maxAttempts:Re,error:n instanceof Error?n.message:String(n)}),i<Re&&await new Promise(r=>setTimeout(r,sn*i))}throw new Error(`Failed to retire hosted Claude session ${e} after ${Re} attempts: ${String(t)}`)}removeOwnedSessionFiles(e){this.removePortFile(e),this.removeOwnedFile(H.join(J.tmpdir(),`codevibe-claude-${e}.pid`)),this.removeOwnedFile(H.join(J.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 i=t[1];this.removeOwnedFile(H.join(J.tmpdir(),`codevibe-claude-instance-${e}.json`)),this.removeOwnedFile(H.join(J.tmpdir(),`codevibe-claude-root-${e}.id`)),this.removeOwnedFile(H.join(J.tmpdir(),`codevibe-claude-tmux-session-${i}`))}removeOwnedFile(e){try{W.unlinkSync(e)}catch(t){t?.code!=="ENOENT"&&s.warn(`Failed to remove owned Claude lifecycle file: ${e}`,t)}}encryptForEmit(e,t,i,n){let r=this.activeSessions.get(n)?.encryptionSnapshot;if(!r)return s.error("No encryption snapshot for target session \u2014 dropping event (fail-closed, #638 H1)",{type:i,sessionId:n,sessionKnown:this.activeSessions.has(n)}),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:i,sessionId:n,encryptionResolved:r.encryptionResolved}),o}async handleEventFromHook(e){let{session_id:t,hook_event_name:i,type:n,content:r}=e;if(s.info("Processing hook event",{sessionId:t,hookEvent:i,type:n}),i==="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:i,owningSessionId:this.currentClaudeSessionId});return}let o,c;try{if(i==="SessionStart")await this.handleSessionStart(e);else if(i==="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:i});let g=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},g,"self-heal");let y=this.claudeToBackendSessionId.get(t)||this.generateBackendSessionId(t);if(!this.activeSessions.has(y)){s.warn("Dropping hook because Claude self-heal lifecycle intent is stale",{claudeSessionId:t,hookEvent:i});return}}let a=this.claudeToBackendSessionId.get(t)||this.generateBackendSessionId(t);if(i==="PostToolUse"&&e.metadata?.tool_name==="AskUserQuestion"){let g=this.activeSessions.get(a),y=this.computeAskUserQuestionFingerprint(e.tool_input?.questions),h=g?.pendingAskUserQuestionInvocations,v=y?h?.findIndex(I=>I.fingerprint===y)??-1:-1;if(g&&h&&v>=0){let[I]=h.splice(v,1);I.toolUseId=e.tool_use_id;let S=g.activeAskUserQuestionInvocation?.occurrenceId===I.occurrenceId;S&&(g.activeAskUserQuestionFingerprint&&(g.completedAskUserQuestionFingerprints||(g.completedAskUserQuestionFingerprints=new Set),g.completedAskUserQuestionFingerprints.add(g.activeAskUserQuestionFingerprint)),this.clearPromptState(g),I.completionEpoch=g.promptStateEpoch,g.activeAskUserQuestionInvocation=void 0);for(let E of I.e1PromptIds)await this.resolveE1PluginApproval(a,E);this.scheduleQueuedMobilePromptDrain(a),s.info("AskUserQuestion resolved by PostToolUse hook",{sessionId:a,occurrenceId:I.occurrenceId,toolUseId:I.toolUseId,promptIds:I.e1PromptIds,clearedCurrentPrompt:S})}else s.warn("Ignoring uncorrelated AskUserQuestion PostToolUse hook",{sessionId:a,toolUseId:e.tool_use_id,completionFingerprint:y?.slice(0,16)})}if(this.toolActivity?.isActive()&&(n===p.EventType.USER_PROMPT||n===p.EventType.ASSISTANT_RESPONSE||n===p.EventType.INTERACTIVE_PROMPT||n===p.EventType.NOTIFICATION)&&await this.toolActivity.flush().catch(g=>s.warn("[tool-activity] boundary flush failed",{sessionId:a,err:String(g)})),i==="UserPromptSubmit"){let g=this.activeSessions.get(a);if(g&&await this.clearInteractivePromptsForAcceptedInput(g),g?.completedAskUserQuestionFingerprints?.size){let y=g.completedAskUserQuestionFingerprints.size;g.completedAskUserQuestionFingerprints.clear(),s.info("Turn boundary \u2014 cleared closed-AskUserQuestion fingerprints",{sessionId:a,clearedCount:y})}}if(n===p.EventType.USER_PROMPT&&e.source===p.EventSource.DESKTOP&&i==="UserPromptSubmit"&&r&&this.isRecentMobilePrompt(a,r)){s.info("Skipping duplicate USER_PROMPT from mobile-originated prompt",{sessionId:a,contentLength:r.length});return}if(n===p.EventType.INTERACTIVE_PROMPT){(typeof e.prompt_id!="string"||e.prompt_id.length===0)&&(e.prompt_id=`synth-${(0,ae.randomUUID)()}`,s.info("Synthesized prompt_id for INTERACTIVE_PROMPT (hook omitted it)",{sessionId:a,synthesizedPromptId:e.prompt_id}));let g=this.activeSessions.get(a),y;if(g&&e.metadata?.tool_name==="AskUserQuestion"){if(y=this.computeAskUserQuestionFingerprint(e.metadata.tool_input?.questions),y){let S=g.activeAskUserQuestionFingerprint===y,E=g.completedAskUserQuestionFingerprints?.has(y)??!1;if(S||E){s.info("Dropping duplicate INTERACTIVE_PROMPT \u2014 AskUserQuestion already tracked",{sessionId:a,fingerprint:y.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,v=Array.isArray(h)&&h.some(S=>S?.multiSelect===!0),I=Array.isArray(h)&&(h[0]?.options?.length??0)===0;if(y&&(v||I)){g.completedAskUserQuestionFingerprints||(g.completedAskUserQuestionFingerprints=new Set),g.completedAskUserQuestionFingerprints.add(y);let S=v?"\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:y.slice(0,16),hasMultiSelect:v,isZeroExplicitOption:I,questionCount:Array.isArray(h)?h.length:0}),setImmediate(()=>this.emitDegradedAUQNotification(a,S));let E=this.newE1Raise(a,!1),F=this.registerAskUserQuestionInvocation(g,y,this.nextPromptGen++,E.promptId);g.activeAskUserQuestionInvocation=F,this.emitSuppressedPromptCarrier(a,E).catch(U=>{s.error("E1: failed to emit degraded-AUQ badge carrier",{sessionId:a,promptId:E.promptId,error:U instanceof Error?U.message:String(U)})});return}}if(g){let h=this.newE1Raise(a,!0);e.prompt_id=h.promptId,this.clearPromptState(g),g.waitingForPromptResponse=!0,g.pendingPromptId=e.prompt_id;let v=this.nextPromptGen++;if(g.promptGenerationToken={promptId:e.prompt_id||"",gen:v},y){g.activeAskUserQuestionFingerprint=y;let I=this.registerAskUserQuestionInvocation(g,y,v,h.promptId);g.activeAskUserQuestionInvocation=I}s.info("Interactive prompt detected - will parse options from tmux",{sessionId:a,promptId:e.prompt_id,tokenGen:v,askUserQuestionFingerprint:y?.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(n===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;n===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:n,sessionId:a,hasSessionKey:!!this.sessionKey,sessionKeyLength:this.sessionKey?.length||0});let d=this.encryptForEmit(r,e.metadata,n,a);if(!d)return;let b=d.content,m=d.metadata,w=d.isEncrypted,f=await this.appSyncClient.createEvent({sessionId:a,type:n,source:e.source,content:b,metadata:m,promptId:e.prompt_id,timestamp:(0,p.prepareEventTimestamp)({orderingKey:a}),isEncrypted:w?!0:void 0});if(o&&c&&((0,p.isPersistedEventResult)(f)?await o.finalizeLegacyOwned([c]):o.clearLegacyInFlight(c)),n===p.EventType.USER_PROMPT&&e.source===p.EventSource.DESKTOP){let g=this.activeSessions.get(a);g?.waitingForPromptResponse&&(this.promoteFingerprintAndClearPromptState(g),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 Te({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 i=gt(t),n={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(i,n,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 i=t.sessionKeyGen??null,n=t.encryptedKeys??[];if(n.length===0)return null;let r=await p.keychainManager.getSessionKey(e,n,i);return r?(s.info("[tool-activity] refreshed session key for SessionKeyStale retry (live key untouched)",{sessionId:e,sessionKeyGen:i}),{sessionKey:r,sessionKeyGen:i}):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,i="explicit"){if(this.isStopping)return Promise.resolve();let n=e.session_id,r=i==="explicit"?this.recordExplicitClaudeSessionStart(n):{ownershipGeneration:this.claudeOwnershipGeneration,sessionIntentVersion:this.getClaudeSessionIntentVersion(n)};return this.enqueueSessionLifecycle(async()=>{if(this.isStopping)return;if(i==="self-heal"){if(!this.canSelfHealClaudeSession(n,r)){s.warn("Rejected stale Claude self-heal request",{claudeSessionId:n,currentClaudeSessionId:this.currentClaudeSessionId});return}this.currentClaudeSessionId===null&&(this.currentClaudeSessionId=n,r.ownershipGeneration=++this.claudeOwnershipGeneration)}else if(!this.isClaudeStartIntentCurrent(n,r))return;let o=++this.lifecycleGeneration;await this.handleSessionStartSerialized(e,t,o,()=>this.isClaudeStartIntentCurrent(n,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 i of this.activeSessions.values())i.claudeSessionId!==e&&this.endedOrSupersededClaudeSessionIds.add(i.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(i=>i.claudeSessionId!==e)}enqueueSessionLifecycle(e){let t=this.sessionStartChain;this.sessionLifecycleQueueDepth+=1;let i=this.sessionLifecycleQueueDepth===1?e():t.catch(()=>{}).then(e),n=i.finally(()=>{this.sessionLifecycleQueueDepth-=1});return this.sessionStartChain=n.catch(()=>{}),i}async handleSessionStartSerialized(e,t,i=this.lifecycleGeneration,n=()=>!0){let r=()=>this.isLifecycleCurrent(i)&&n();if(!r())return;let o=e.session_id,c=this.appSyncClient.getCurrentUserId(),a=this.claudeToBackendSessionId.get(o),u=t||a?null:await this.sessionBindingStore.read(c,o);if(!r())return;let d=t??a??u??this.generateBackendSessionId(o),b=e.metadata?.cwd||process.cwd();this.claudeToBackendSessionId.set(o,d),s.info("Session started",{claudeSessionId:o,sessionId:d,cwd:b});let m=this.activeSessions.get(d);if(m){if(m.mobileEndWatcher?.stop(),m.mobileEndWatcher=void 0,m.orchestrationPromptWatcher?.stop(),m.orchestrationPromptWatcher=void 0,m.orchestrationDecisionWatcher?.stop(),m.orchestrationDecisionWatcher=void 0,m.orchestrationClient){try{await m.orchestrationClient.destroy()}catch(h){s.warn("[v1-bridge] OrchestrationClient.destroy() failed on same-session restart",{sessionId:d,error:String(h)})}m.orchestrationClient=void 0}this.appSyncClient.stopHeartbeat(d),this.appSyncClient.cleanupSubscription(d),this.activeSessions.get(d)===m&&this.activeSessions.delete(d)}let w=Array.from(this.activeSessions.keys()).filter(h=>h!==d);if(w.length>0){s.info(`Marking ${w.length} previous session(s) as INACTIVE`);for(let h of w){if(this.toolActivity&&this.toolActivity.sessionId===h){let I=this.toolActivity,S=!1;for(let E=1;E<=2&&!S;E+=1)try{await I.stop(),S=!0}catch(F){s.warn("[tool-activity] outgoing consolidator drain failed on resume",{prevId:h,attempt:E,error:String(F)})}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 v=this.activeSessions.get(h);if(v?.mobileEndWatcher&&(v.mobileEndWatcher.stop(),v.mobileEndWatcher=void 0),v?.orchestrationPromptWatcher&&(v.orchestrationPromptWatcher.stop(),v.orchestrationPromptWatcher=void 0),v?.orchestrationDecisionWatcher&&(v.orchestrationDecisionWatcher.stop(),v.orchestrationDecisionWatcher=void 0),v?.orchestrationClient){try{await v.orchestrationClient.destroy()}catch(I){s.warn("[v1-bridge] OrchestrationClient.destroy() failed on resume",{prevId:h,error:String(I)})}v.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(I){s.warn("Failed to mark previous session as INACTIVE",{prevId:h,error:I})}v&&this.removePortFile(v.claudeSessionId),this.activeSessions.delete(h)}}this.writePortFile(o);let f={sessionId:d,claudeSessionId:o,userId:c,projectPath:b,cwd:b,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,f.encryptionSnapshot={sessionKey:null,sessionIsEncrypted:!1,encryptionResolved:!1},this.activeSessions.set(d,f);try{let h;try{h=await(0,p.resumeOrCreateSession)({sessionId:d,userId:f.userId,agentType:p.AgentType.CLAUDE,projectPath:b,metadata:e.metadata||{}},this.appSyncClient,s)}catch(v){if(!(0,p.isRetiredSessionCannotReactivateError)(v))throw v;if(!r()||this.activeSessions.get(d)!==f){this.activeSessions.get(d)===f&&this.activeSessions.delete(d),this.claudeToBackendSessionId.get(o)===d&&this.claudeToBackendSessionId.delete(o),this.removePortFile(o);return}let I=d;this.appSyncClient.stopHeartbeat(I),this.appSyncClient.cleanupSubscription(I),this.activeSessions.delete(I);let S=Ne(c,o,I);if(await this.sessionBindingStore.write(c,o,S),!r())return;d=S,f.sessionId=d,this.activeSessions.set(d,f),this.claudeToBackendSessionId.set(o,d),s.warn("Backend rejected retired Claude generation during SessionStart; creating replacement row",{claudeSessionId:o,retiredSessionId:I,replacementSessionId:d}),h=await(0,p.resumeOrCreateSession)({sessionId:d,userId:f.userId,agentType:p.AgentType.CLAUDE,projectPath:b,metadata:e.metadata||{}},this.appSyncClient,s)}if(!r()){await this.retireCancelledClaudeBootstrap({requestedSessionId:d,authoritativeSessionId:h.sessionId,claudeSessionId:o,sessionState:f});return}if(this.activeSessions.get(d)!==f){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:f});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:f});return}this.activeSessions.get(d)===f&&this.activeSessions.delete(d),d=h.sessionId,f.sessionId=d,this.activeSessions.set(d,f),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,f.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:f});return}if(this.sessionKey){let v=this.buildToolActivityIntegration();try{if(await v.start(d,e.transcript_path,{hostingSinceMs:e.received_at_ms}),!r()){await v.stop().catch(()=>{}),await this.retireCancelledClaudeBootstrap({requestedSessionId:d,authoritativeSessionId:h.sessionId,claudeSessionId:o,sessionState:f});return}this.toolActivity=v}catch(I){s.warn("[tool-activity] consolidator start failed \u2014 keeping per-tool TOOL_USE",{sessionId:d,error:String(I)}),this.toolActivity=null,await v.stop().catch(()=>{})}}}catch(h){if(!r()){await this.retireCancelledClaudeBootstrap({requestedSessionId:d,authoritativeSessionId:this.authoritativeSessionIdFromError(h)??d,claudeSessionId:o,sessionState:f});return}let v=h?.authoritativeSessionId;if(typeof v=="string"&&v.length>0&&v!==d&&this.activeSessions.get(d)===f&&(this.activeSessions.delete(d),d=v,f.sessionId=d,this.activeSessions.set(d,f),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)===f&&this.activeSessions.delete(d),this.removePortFile(o);return}if(h?.code==="ENCRYPTED_SESSION_NO_KEY"){this.sessionIsEncrypted=!0,this.sessionKey=null,this.encryptionResolved=!0,f.encryptionSnapshot={sessionKey:null,sessionIsEncrypted:!0,encryptionResolved:!0};let I=this.enterEncryptedKeyRecovery(e,d);this.appSyncClient.cleanupSubscription(d),await this.invalidateEncryptedRecoveryLiveness(I),s.error("Failed to create/resume session:",h);return}else{this.activeSessions.get(d)===f&&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:f});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)===f&&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 v=this.enterEncryptedKeyRecovery(e,d);this.appSyncClient.cleanupSubscription(d),await this.invalidateEncryptedRecoveryLiveness(v);return}}if(!r()){await this.retireCancelledClaudeBootstrap({requestedSessionId:d,authoritativeSessionId:d,claudeSessionId:o,sessionState:f});return}let y=this.activeSessions.get(d);if(y){y.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(I){s.warn("Failed to mark session INACTIVE on mobile-end",{sessionId:d,error:I})}let v=process.env[ie];if(!v){s.warn("No tmux session set; skipping desktop self-terminate",{sessionId:d,expectedEnv:ie});return}await dn(v,tn)});let h=!1;try{y.orchestrationPromptWatcher=this.subscribeToOrchestrationPrompts(d)}catch(v){h=!0,s.warn("[v1-bridge] Failed to start orchestration prompt watcher",{sessionId:d,error:String(v)})}try{y.orchestrationDecisionWatcher=await this.subscribeToApplyUserDecision(y)}catch(v){h=!0,s.warn("[v1-bridge] Failed to start applyUserDecision watcher",{sessionId:d,error:String(v)})}h&&this.emitBridgeUnavailableBanner()}this.clearEncryptedKeyRecovery("session key recovered and liveness established",o,d)}recoverRetiredBackendSession(e,t){let i=this.retiredSessionRecoveryPromises.get(e);if(i)return i;if(this.isStopping)return Promise.resolve();let n=t.claudeSessionId,r={ownershipGeneration:this.claudeOwnershipGeneration,sessionIntentVersion:this.getClaudeSessionIntentVersion(n)},o=()=>this.isClaudeStartIntentCurrent(n,r),a=this.enqueueSessionLifecycle(async()=>{if(this.isStopping||!o()||this.activeSessions.get(e)!==t||this.claudeToBackendSessionId.get(t.claudeSessionId)!==e)return;let u={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 b=Ne(t.userId,t.claudeSessionId,e);if(await this.sessionBindingStore.write(t.userId,t.claudeSessionId,b),!o())return;let m=++this.lifecycleGeneration;await this.handleSessionEndSerialized({session_id:t.claudeSessionId,metadata:{reason:"backend-generation-retired"}}),!(!this.isLifecycleCurrent(m)||!o())&&await this.handleSessionStartSerialized(u,b,m,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 i of t)this.appSyncClient.stopHeartbeat(i),this.appSyncClient.cleanupSubscription(i);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(i){s.warn("Cancelled Claude bootstrap row could not be marked INACTIVE",{sessionId:e.authoritativeSessionId,error:i instanceof Error?i.message:String(i)})}}async emitBridgeUnavailableBanner(){let e=process.env[ie],t="[CodeVibe] Cross-device decision sync unavailable \u2014 mobile responses will be sent as free-text";if(s.warn(t),!!e)try{await Oe(`tmux display-message -t "${e}" -- ${JSON.stringify(t)}`)}catch(i){s.warn("[v1-bridge] tmux display-message for bridge-unavailable banner failed",{tmuxSession:e,error:String(i)})}}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,i=this.claudeToBackendSessionId.get(t)||this.generateBackendSessionId(t);s.info("Session ended",{claudeSessionId:t,sessionId:i,reason:e.metadata?.reason});let n=this.activeSessions.get(i);if(n?.mobileEndWatcher&&(n.mobileEndWatcher.stop(),n.mobileEndWatcher=void 0),n?.orchestrationPromptWatcher&&(n.orchestrationPromptWatcher.stop(),n.orchestrationPromptWatcher=void 0),n?.orchestrationDecisionWatcher&&(n.orchestrationDecisionWatcher.stop(),n.orchestrationDecisionWatcher=void 0),n?.orchestrationClient){try{await n.orchestrationClient.destroy()}catch(c){s.warn("[v1-bridge] OrchestrationClient.destroy() failed",{sessionId:i,error:String(c)})}n.orchestrationClient=void 0}this.toolActivity&&this.toolActivity.sessionId===i&&(await this.toolActivity.stop(),this.toolActivity=null),this.activeSessions.delete(i),this.claudeToBackendSessionId.delete(t),this.removePortFile(t),n?.waitingForPromptResponse&&(s.info("Clearing prompt wait state - session ending",{sessionId:i}),this.clearPromptState(n));let r=this.queuedMobilePromptTimers.get(i);r&&clearTimeout(r),this.queuedMobilePromptTimers.delete(i),this.queuedMobilePrompts.delete(i);let o=this.toolActivityLegacyRecorders.get(i);if(o&&o.disposeLegacyCoordination(),this.toolActivityLegacyRecorders.delete(i),this.appSyncClient.stopHeartbeat(i),this.appSyncClient.cleanupSubscription(i),n)try{await this.appSyncClient.updateSession({sessionId:i,status:p.SessionStatus.INACTIVE}),s.info("Session marked as INACTIVE in AppSync",{sessionId:i})}catch(c){s.warn("Failed to update session in AppSync:",c)}else s.warn("Cannot update session - session state not found",{sessionId:i});s.debug("Session cleanup completed",{sessionId:i})}async withMobilePromptAdmission(e,t){let i,r=(this.queuedMobilePromptAdmissions.get(e)??Promise.resolve()).catch(()=>{}).then(async()=>{i=await t()});this.queuedMobilePromptAdmissions.set(e,r);try{return await r,i}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 i=this.claudeToBackendSessionId.get(t)??e,n=this.activeSessions.get(i);if(!n||n.claudeSessionId!==t)throw new Error(`Cannot recover mobile queue for inactive native session ${t}`);let r=await this.mobilePromptQueueStore.read(t);for(let u of r)u.sessionId!==i&&(u.sessionId=i,await this.mobilePromptQueueStore.put(t,u)),u.state==="sending"&&(u.state="terminal",u.lastError="CodeVibe restarted while sending this message. It was not retried to avoid submitting it twice.",await this.mobilePromptQueueStore.put(t,u));let o=this.queuedMobilePrompts.get(i)??[],c=new Map;for(let u of[...r,...o])c.set(u.event.eventId,u);let a=[...c.values()];if(a.length>0){this.queuedMobilePrompts.set(i,a),n.processedEventIds||(n.processedEventIds=new Set);for(let u of a)n.processedEventIds.add(u.event.eventId);this.scheduleQueuedMobilePromptDrain(i)}}async enqueueMobilePrompt(e,t,i,n){let r=n??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,i))}async enqueueMobilePromptOnce(e,t,i,n){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===i.eventId);if(a){let d=a.sessionId!==r;return a.sessionId=r,n&&!a.pendingDismissal&&(a.pendingDismissal=n,d=!0),d&&await this.mobilePromptQueueStore.put(t,a),o&&(o.processedEventIds||(o.processedEventIds=new Set),o.processedEventIds.add(i.eventId),this.queuedMobilePrompts.set(r,c),n||this.scheduleQueuedMobilePromptDrain(r)),a}let u={version:2,nativeSessionId:t,sessionId:r,event:i,state:"queued",attempts:0,nextAttemptAt:Date.now(),pendingDismissal:n};return await this.mobilePromptQueueStore.put(t,u),c.push(u),o&&this.activeSessions.get(r)===o&&(this.queuedMobilePrompts.set(r,c),o.processedEventIds||(o.processedEventIds=new Set),o.processedEventIds.add(i.eventId),n||this.scheduleQueuedMobilePromptDrain(r)),u}scheduleQueuedMobilePromptDrain(e,t=0){let i=this.queuedMobilePromptTimers.get(e);i&&clearTimeout(i);let n=setTimeout(()=>{this.queuedMobilePromptTimers.delete(e),this.drainQueuedMobilePrompts(e)},Math.max(0,t));n.unref?.(),this.queuedMobilePromptTimers.set(e,n)}queuedPromptBackoffMs(e){return Math.min(l.MOBILE_PROMPT_QUEUE_MAX_BACKOFF_MS,500*2**Math.max(0,e-1))}async terminalizeQueuedMobilePrompt(e,t){try{if(!t.terminalNoticeSent){if(!await this.sendPromptError(e.sessionId,t.lastError??"The queued message could not be sent. Please retry it."))return s.warn("Queued mobile-prompt terminal notice dropped by the encryption chokepoint; retaining the record",{sessionId:e.sessionId,eventId:t.event.eventId}),!1;t.terminalNoticeSent=!0,await this.mobilePromptQueueStore.put(t.nativeSessionId,t)}return await this.acknowledgeQueuedMobilePromptExecuted(e,t)?(await this.mobilePromptQueueStore.remove(t.nativeSessionId,t.event.eventId),!0):!1}catch(i){return s.warn("Failed to publish queued mobile-prompt terminal disposition",{sessionId:e.sessionId,eventId:t.event.eventId,error:String(i)}),!1}}async acknowledgeQueuedMobilePromptExecuted(e,t){try{if(de("reject-executed-ack"))throw s.warn("DEV FAULT reject-executed-ack: simulating a failed EXECUTED acknowledgement",{eventId:t.event.eventId}),new Error("DEV FAULT: simulated EXECUTED acknowledgement failure");let i=await this.appSyncClient.updateEventStatus({eventId:t.event.eventId,sessionId:t.event.sessionId,timestamp:t.event.timestamp,deliveryStatus:p.DeliveryStatus.EXECUTED});return i?.eventId===t.event.eventId&&i?.sessionId===t.event.sessionId&&i?.timestamp===t.event.timestamp&&i?.deliveryStatus===p.DeliveryStatus.EXECUTED?(e.processedEventIds||(e.processedEventIds=new Set),e.processedEventIds.add(t.event.eventId),this.queuedAckRepairs.delete(t.event.eventId),this.queuedAckFailures.delete(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:i?.eventId,acknowledgedSessionId:i?.sessionId,acknowledgedTimestamp:i?.timestamp,acknowledgedDeliveryStatus:i?.deliveryStatus}),!1)}catch(i){let n=String(i);if(n.includes("INVALID_DELIVERY_STATUS_TRANSITION")&&!this.queuedAckRepairs.has(t.event.eventId)){s.warn("Queued mobile-prompt EXECUTED acknowledgement rejected; repairing SENT\u2192DELIVERED first",{sessionId:e.sessionId,eventId:t.event.eventId});try{return await this.writeEventStatus(t.event,p.DeliveryStatus.DELIVERED),this.queuedAckRepairs.add(t.event.eventId),this.acknowledgeQueuedMobilePromptExecuted(e,t)}catch(r){return s.warn("Queued mobile-prompt DELIVERED repair failed",{sessionId:e.sessionId,eventId:t.event.eventId,error:String(r)}),!1}}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:n}),!1}}async abandonQueuedAcknowledgementIfExhausted(e,t){if(t.state==="terminal"&&t.terminalNoticeSent!==!0)return!1;let i=(this.queuedAckFailures.get(t.event.eventId)??0)+1;if(this.queuedAckFailures.set(t.event.eventId,i),i<l.MOBILE_PROMPT_QUEUE_MAX_ATTEMPTS)return!1;s.error("Abandoning the queued mobile-prompt EXECUTED acknowledgement so later prompts can drain",{sessionId:e.sessionId,eventId:t.event.eventId,state:t.state,failures:i});try{await this.mobilePromptQueueStore.remove(t.nativeSessionId,t.event.eventId)}catch(n){s.warn("Failed to remove the abandoned queue record",{sessionId:e.sessionId,eventId:t.event.eventId,error:String(n)})}return this.queuedAckRepairs.delete(t.event.eventId),this.queuedAckFailures.delete(t.event.eventId),!0}async reconcileQueuedPromptDismissal(e,t){let i=t.pendingDismissal;if(!i)return"resolved";let n=i.invocation.occurrenceId,r=e.pendingAskUserQuestionInvocations?.some(c=>c.occurrenceId===n)??!1,o=e.activeAskUserQuestionInvocation?.occurrenceId;return!r&&o!==n?(i.dismissalSent&&await this.settleDismissedAskUserQuestionOccurrence(e,i.invocation),t.pendingDismissal=void 0,await this.mobilePromptQueueStore.put(t.nativeSessionId,t),"resolved"):o!==n||e.pendingPanePromptIdentity?.hookOccurrenceId!==n?(o&&o!==n&&await this.settleDismissedAskUserQuestionOccurrence(e,i.invocation),t.pendingDismissal=void 0,await this.mobilePromptQueueStore.put(t.nativeSessionId,t),"resolved"):i.nextAttemptAt>Date.now()?(this.scheduleQueuedMobilePromptDrain(e.sessionId,i.nextAttemptAt-Date.now()),"retry"):await this.resolveAskUserQuestionAfterVerifiedDismissal(e,i.expectedIdentity,i.invocation,{attempts:1,delayMs:0})?(t.pendingDismissal=void 0,await this.mobilePromptQueueStore.put(t.nativeSessionId,t),"resolved"):(i.attempts+=1,i.attempts>=l.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"):(i.nextAttemptAt=Date.now()+this.queuedPromptBackoffMs(i.attempts),t.lastError="Waiting for the dismissed desktop chooser to close",await this.mobilePromptQueueStore.put(t.nativeSessionId,t),this.scheduleQueuedMobilePromptDrain(e.sessionId,i.nextAttemptAt-Date.now()),"retry"))}async drainQueuedMobilePrompts(e){if(this.queuedMobilePromptDrains.has(e))return;let t=this.activeSessions.get(e),i=this.queuedMobilePrompts.get(e);if(!t||!i?.length)return;this.queuedMobilePromptDrains.add(e);let n=new Set;try{for(;i.length>0;){if(this.activeSessions.get(e)!==t)return;let r=i[0];if(r.state==="submitted"){if(!await this.acknowledgeQueuedMobilePromptExecuted(t,r)){if(await this.abandonQueuedAcknowledgementIfExhausted(t,r)){i.shift();continue}this.scheduleQueuedMobilePromptDrain(e,2e3);return}await this.mobilePromptQueueStore.remove(r.nativeSessionId,r.event.eventId),i.shift();continue}if(r.state==="terminal"){if(!await this.terminalizeQueuedMobilePrompt(t,r)){if(await this.abandonQueuedAcknowledgementIfExhausted(t,r)){i.shift();continue}if(r.terminalNoticeSent!==!0&&!n.has(r.event.eventId)){n.add(r.event.eventId),i.push(i.shift());continue}this.scheduleQueuedMobilePromptDrain(e,2e3);return}i.shift();continue}let o=await this.reconcileQueuedPromptDismissal(t,r);if(o==="retry")return;if(o==="terminal")continue;if(t.activeAskUserQuestionInvocation||t.waitingForPromptResponse||t.promptGenerationToken!==void 0){this.scheduleQueuedMobilePromptDrain(e,500);return}let c=r.nextAttemptAt-Date.now();if(c>0){this.scheduleQueuedMobilePromptDrain(e,c);return}if(!await this.waitForChooserAbsence(e,t,{attempts:1,delayMs:0})){r.attempts+=1,r.attempts>=l.MOBILE_PROMPT_QUEUE_MAX_ATTEMPTS?(r.state="terminal",r.lastError="The desktop chooser did not close, so this message was not submitted. Please retry it."):(r.nextAttemptAt=Date.now()+this.queuedPromptBackoffMs(r.attempts),r.lastError="Waiting for the desktop chooser to close"),await this.mobilePromptQueueStore.put(r.nativeSessionId,r);continue}if(de("force-terminal")){s.warn("DEV FAULT force-terminal: marking the queued prompt terminal instead of sending it",{eventId:r.event.eventId}),r.state="terminal",r.lastError="The desktop chooser did not close, so this message was not submitted. Please retry it.",await this.mobilePromptQueueStore.put(r.nativeSessionId,r);continue}if(r.state="sending",r.lastError=void 0,await this.mobilePromptQueueStore.put(r.nativeSessionId,r),await this.executeMobilePrompt(e,r.event,{suppressFailureNotification:!0,deferStatusUpdate:!0})){r.state="submitted",await this.mobilePromptQueueStore.put(r.nativeSessionId,r);continue}r.state="terminal",r.lastError="CodeVibe could not prove whether this message reached Claude. It was not retried to avoid submitting it twice.",await this.mobilePromptQueueStore.put(r.nativeSessionId,r)}this.queuedMobilePrompts.delete(e)}finally{this.queuedMobilePromptDrains.delete(e),i.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 i=>this.dispatchMobileEvent(e,i),i=>{s.error("Subscription error",{sessionId:e,error:i})},{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 i=t.content||"";if(t.isEncrypted&&this.sessionKey)try{i=p.cryptoService.decryptContent(t.content,this.sessionKey),s.debug("Event decrypted successfully",{eventId:t.eventId})}catch(f){s.error("Failed to decrypt event:",{eventId:t.eventId,error:f}),i=t.content}let n,r,o=!1,c,a,u,d,b;if(t.type===p.EventType.USER_PROMPT||t.type===p.EventType.PROMPT_RESPONSE)if(r=this.activeSessions.get(e),b=(0,p.classifyMobilePromptInput)(i,Object.keys(r?.pendingSubmitMap??{})),!r)n="no-session";else if(r.processedEventIds?.has(t.eventId))n="skip-dedup";else if(r.inFlightEventIds?.has(t.eventId))n="drop-event-redeliver";else if(r.waitingForPromptResponse){let f=r.promptGenerationToken;if(!f)n="regular";else if(t.type===p.EventType.USER_PROMPT&&b.kind==="option"&&r.hasReceivedPromptResponse&&(!t.promptId||t.promptId.length===0))n="drop-stale-answer";else if(b.kind==="option"&&t.type===p.EventType.PROMPT_RESPONSE&&(!t.promptId||f.promptId.length===0||t.promptId!==f.promptId))n="drop-stale-answer";else if(b.kind==="option"&&t.promptId&&f.promptId.length>0&&t.promptId!==f.promptId)n="drop-stale-answer";else{let g=f.promptId.length>0?f.promptId:`__prompt_gen_${f.gen}`;r.inFlightPromptIds?.has(g)?n="drop-in-flight":(r.inFlightPromptIds||(r.inFlightPromptIds=new Set),r.inFlightEventIds||(r.inFlightEventIds=new Set),r.inFlightPromptIds.add(g),r.inFlightEventIds.add(t.eventId),o=!0,u=g,d=t.eventId,c={promptId:f.promptId,gen:f.gen},a={pendingPromptId:r.pendingPromptId,pendingSubmitMap:r.pendingSubmitMap?{...r.pendingSubmitMap}:void 0,pendingPanePromptIdentity:r.pendingPanePromptIdentity,activeAskUserQuestionInvocation:r.activeAskUserQuestionInvocation},n="walker")}}else n="regular";else n="not-user-prompt";let w={...t,content:i};if(n!=="skip-dedup")if(de("skip-delivered"))s.warn("DEV FAULT skip-delivered: leaving the event SENT (no DELIVERED mark)",{eventId:t.eventId});else 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(f){s.warn("Failed to mark event as DELIVERED",{eventId:t.eventId,error:f})}if(t.type===p.EventType.USER_PROMPT&&r?.pendingOrchestrationGate&&await this.handleOrchestrationPromptResponse(r,i,t)){u&&r.inFlightPromptIds?.delete(u),d&&r.inFlightEventIds?.delete(d);return}if(n==="skip-dedup"){s.info("[walker] Subscription-level dedup hit (already processed) \u2014 skipping",{sessionId:e,eventId:t.eventId});return}if(n==="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(f){s.warn("[walker] markEventExecuted threw on stale-answer drop \u2014 relying on processedEventIds Set",{sessionId:e,eventId:t.eventId,error:String(f)})}return}if(n==="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(f){s.warn("[walker] markEventExecuted threw on subscription-level duplicate drop \u2014 relying on processedEventIds Set",{sessionId:e,eventId:t.eventId,error:String(f)})}return}if(n==="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(n==="walker"){t.type===p.EventType.PROMPT_RESPONSE&&b?.kind==="option"&&r&&(r.hasReceivedPromptResponse=!0),await this.handleMobilePromptResponse(e,t,i,r,o,c,u,d,a);return}if(n==="regular"){if(t.type===p.EventType.PROMPT_RESPONSE&&b?.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(f){s.warn("markEventExecuted threw on PROMPT_RESPONSE orphan drop \u2014 relying on processedEventIds Set",{sessionId:e,eventId:t.eventId,error:String(f)})}return}await this.executeMobilePrompt(e,w);return}if(n==="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,i,n,r=!1,o,c,a,u){let d=o??n.promptGenerationToken,b=c,m=a;if(!r&&d){let w=d.promptId.length>0?d.promptId:`__prompt_gen_${d.gen}`;if(n.inFlightPromptIds?.has(w)){s.warn("[walker] Duplicate mobile USER_PROMPT for same prompt \u2014 dropping",{sessionId:e,eventId:t.eventId,lockKey:w}),await this.markEventExecutedIdempotent(n,t);return}n.inFlightPromptIds||(n.inFlightPromptIds=new Set),n.inFlightEventIds||(n.inFlightEventIds=new Set),n.inFlightPromptIds.add(w),n.inFlightEventIds.add(t.eventId),b=w,m=t.eventId}try{if(!r&&n.processedEventIds?.has(t.eventId)){s.info("[walker] Redelivered event already processed \u2014 skipping",{sessionId:e,eventId:t.eventId});return}let w=i.trim(),f=u?u.pendingPromptId:n.pendingPromptId,g=u?u.pendingSubmitMap:n.pendingSubmitMap,y=u?u.pendingPanePromptIdentity:n.pendingPanePromptIdentity,h=u?u.activeAskUserQuestionInvocation:n.activeAskUserQuestionInvocation,v=g,I=this.parseInteractivePromptInput(w,Object.keys(v??{}));if(s.info("Parsed interactive prompt input",{sessionId:e,content:w,parsed:I,hasSubmitMap:!!v}),I.action==="send_as_response"&&y?.kind==="ask_user_question"){let F=n.promptGenerationToken,U=this.activeSessions.get(e)===n&&F?.gen===d?.gen&&F?.promptId===d?.promptId,T=h,R=U&&T?{expectedIdentity:y,invocation:T,attempts:0,nextAttemptAt:Date.now()}:void 0,A=await this.enqueueMobilePrompt(e,{...t,content:i},R,n.claudeSessionId);if(!U||!T){this.scheduleQueuedMobilePromptDrain(e);return}let O=await this.dismissLivePromptForConversation(n,d);O&&A.pendingDismissal&&(A.pendingDismissal.dismissalSent=!0,await this.mobilePromptQueueStore.put(A.nativeSessionId,A));let Q=O&&T&&await this.resolveAskUserQuestionAfterVerifiedDismissal(n,y,T);Q&&A.pendingDismissal&&(A.pendingDismissal=void 0,await this.mobilePromptQueueStore.put(A.nativeSessionId,A)),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,n,S))return!1;let F=n.promptGenerationToken,U=F?.gen,T=d?.gen;return this.activeSessions.get(e)!==n||U!==T?(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:F}),!0):!1};if(E()){await this.markEventExecutedIdempotent(n,t);return}if(I.action==="select_option"){let F=v?.[I.option]||I.option,U=n.pendingQuestionsQueue!==void 0,T=n.pendingCurrentQuestion?.options?.length??0,R=I.option?parseInt(I.option,10):NaN,A=n.pendingSynthTail?.length??0;if(U&&n.pendingCurrentQuestion!==void 0&&A>0&&Number.isFinite(R)&&R>T&&R<=T+A){if(await this.markEventExecutedIdempotent(n,t),E())return;let D=await this.sendVerifiedPromptKey(n,y,"Escape");if(E())return;if(!(D&&y?.kind==="ask_user_question"&&h&&await this.resolveAskUserQuestionAfterVerifiedDismissal(n,y,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:I.option,terminalInput:F,isV2AskUserQuestion:U});let Q=parseInt(I.option,10)-1,C=n.pendingCurrentQuestion?.options?.[Q],_=typeof C=="string"?C:C&&typeof C=="object"?C.label:`option ${I.option}`,V=n.pendingCurrentQuestion?._isSubmit===!0,L=await this.answerVerifiedInteractivePrompt(n,y,F,{pressEnter:!U});if(E()){await this.markEventExecutedIdempotent(n,t);return}if(L){if(await this.markEventExecutedIdempotent(n,t),E())return;if(!f){s.warn("emitAnswerAck called without promptId \u2014 clearing state + skipping ack",{sessionId:e,source:"select_option",eventId:t.eventId}),this.promoteFingerprintAndClearPromptState(n);return}let D=(n.pendingQuestionsQueue?.length??0)===0;try{if(U){let Y=_,ue=V,oe=Y.toLowerCase(),fe;ue&&oe==="cancel"?fe="\u2192 Cancel \u2014 AskUserQuestion cancelled, no answers submitted":ue&&oe.startsWith("submit")?fe="\u2192 Submit answers \u2014 AskUserQuestion completed":fe=`\u2192 ${Y}`,await this.emitUserChoice(e,fe)}else await this.emitAnswerAck(e,`Selected option ${I.option}`,{promptId:f,questionIndex:0,isTerminal:D})}catch(Y){s.warn("[walker] user-choice/ack emit failed \u2014 continuing to STEP 7/8",{sessionId:e,promptId:f,isV2AskUserQuestion:U,error:Y instanceof Error?Y.message:String(Y)})}if(E())return;let $=n.pendingQuestionsQueue?.shift();if($&&(n.pendingCurrentQuestion=$,n.pendingPanePromptIdentity=this.buildAskUserQuestionPaneIdentity($,n.activeAskUserQuestionInvocation?.occurrenceId)),!$){n.activeAskUserQuestionFingerprint&&(n.completedAskUserQuestionFingerprints||(n.completedAskUserQuestionFingerprints=new Set),n.completedAskUserQuestionFingerprints.add(n.activeAskUserQuestionFingerprint),s.info("AskUserQuestion V2 walker complete \u2014 fingerprint marked closed",{sessionId:e,fingerprint:n.activeAskUserQuestionFingerprint.slice(0,16)})),this.clearPromptState(n);return}let ee=this.newE1Raise(e,!0),N=ee.promptId;if(n.activeAskUserQuestionInvocation?.e1PromptIds.push(N),!N){this.promoteFingerprintAndClearPromptState(n),s.warn("Q[next] emit aborted: synthesized promptId was empty; promoted fingerprint + cleared prompt state",{sessionId:e,eventId:t.eventId});return}let te=n.pendingSynthTail??[],Z=this.buildQuestionWireData($,te),M=$.question,k={tool_name:"AskUserQuestion",tool_input:{questions:[$]},options:Z.options,submitMap:Z.submitMap,instructions:Z.instructions},P=n.encryptionSnapshot,K=(0,p.encryptForEmit)(P?.sessionKey??null,P?.sessionIsEncrypted??!1,M,k,P?P.encryptionResolved:!1);if(!K){s.error("No session key for ENCRYPTED session \u2014 dropping Q[next] prompt (fail-closed, #638)",{sessionId:e,promptId:N}),this.promoteFingerprintAndClearPromptState(n);return}let j=K.content,re=K.metadata,se=K.isEncrypted,ne=this.nextPromptGen++,ce={promptId:N,gen:ne};n.pendingPromptId=N,n.pendingSubmitMap=Z.submitMap,n.promptGenerationToken=ce;let X=ce,he=this.activeSessions.get(e)?.promptGenerationToken;if(!he||he.gen!==X.gen||he.promptId!==X.promptId){s.warn("Q[next] emit aborted: token replaced before await dispatch",{sessionId:e,tokenAtAwait:X,currentToken:he});return}let He=N.length>0?N:`__prompt_gen_${ce.gen}`;n.inFlightPromptIds||(n.inFlightPromptIds=new Set),n.inFlightPromptIds.add(He);try{this.stashE1RaiseContent(N,M,k);try{await this.promptPublications.run(N,()=>this.appSyncClient.createEvent({sessionId:e,type:p.EventType.INTERACTIVE_PROMPT,source:p.EventSource.DESKTOP,content:j,metadata:re,...(0,p.raiseFieldsFromRecord)(ee),timestamp:(0,p.prepareEventTimestamp)({orderingKey:e}),isEncrypted:se?!0:void 0})),s.info("Q[next] emit succeeded",{sessionId:e,promptId:N,remaining:n.pendingQuestionsQueue?.length??0})}catch(Y){let ue=this.activeSessions.get(e),oe=ue?.promptGenerationToken;oe&&oe.gen===X.gen&&oe.promptId===X.promptId?(this.promoteFingerprintAndClearPromptState(ue),s.warn("Q[next] emit failed; promoted fingerprint + cleared prompt state. User must answer remaining questions on desktop terminal.",{sessionId:e,promptId:N,error:Y instanceof Error?Y.message:String(Y)})):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:X,currentToken:oe,error:Y instanceof Error?Y.message:String(Y)})}}finally{n.inFlightPromptIds.delete(He)}}else await this.publishRefusalThenMark(n,t,this.describeAskUserQuestionRefusal(n))}else if(I.action==="option_with_followup"){if(n.pendingQuestionsQueue!==void 0){let R=this.getWalkerPosition(n),A=n.pendingCurrentQuestion?.options?.length??0,O=I.option?parseInt(I.option,10):NaN,Q=Number.isFinite(O)&&O>A,C=I.followUpText??"";if(s.info("AUQ walker \u2014 option_with_followup dispatch",{sessionId:e,option:I.option,optionNum:O,explicitCount:A,isSynthDigit:Q,walkerPosition:R,followUpTextLen:C.length}),await this.markEventExecutedIdempotent(n,t),E())return;if(Q){if(R==="on_synth"){let M=await this.answerVerifiedInteractivePrompt(n,y,"2",{pressEnter:!1});if(E())return;if(!M){try{await this.emitUserChoice(e,`AskUserQuestion was not cancelled \u2014 ${this.describeAskUserQuestionRefusal(n).replace(/\.?$/,".")} Your reply was not sent.`)}catch(k){s.warn("emitUserChoice on synth-digit on-SYNTH Cancel failed",{sessionId:e,error:k instanceof Error?k.message:String(k)})}return}if(await new Promise(k=>setTimeout(k,1500)),E())return;if(C){let k=await this.sendFreeFormWhenNoChooser(e,n,C,S);if(E())return;k||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(k){s.warn("emitUserChoice on synth-digit on-SYNTH success failed",{sessionId:e,error:k instanceof Error?k.message:String(k)})}if(E())return;this.promoteFingerprintAndClearPromptState(n);return}await this.handleMobileReplyAsDismissAndPrompt(e,n,y,C,S,E);return}let _=v?.[I.option]||I.option,V=O-1,L=n.pendingCurrentQuestion?.options?.[V],D=typeof L=="string"?L:L&&typeof L=="object"?L.label:`option ${I.option}`,$=n.pendingCurrentQuestion?._isSubmit===!0;if(R==="q1"){let M=await this.answerVerifiedInteractivePrompt(n,y,_,{pressEnter:!1});if(E())return;if(!M){try{await this.publishNotificationWithRetries(e,`${this.describeAskUserQuestionRefusal(n).replace(/\.?$/,".")} Your reply (including the follow-up text) was not sent.`,{eventId:t.eventId,purpose:"refusal-followup-q1"})}catch(k){s.warn("sendPromptError on q1 option_with_followup failed",{sessionId:e,error:k instanceof Error?k.message:String(k)})}return}if(await new Promise(k=>setTimeout(k,1500)),E())return;if(C){let k=await this.sendFreeFormWhenNoChooser(e,n,C,S);if(E())return;k||s.warn("q1 option_with_followup followup text-send failed",{sessionId:e})}try{await this.emitUserChoice(e,C?`\u2192 ${D} + sending your reply as a new prompt`:`\u2192 ${D}`)}catch(k){s.warn("emitUserChoice on q1 option_with_followup success failed",{sessionId:e,error:k instanceof Error?k.message:String(k)})}if(E())return;this.promoteFingerprintAndClearPromptState(n);return}if(R==="nq_mid"){let M=await this.answerVerifiedInteractivePrompt(n,y,_,{pressEnter:!1});if(E())return;if(!M){try{await this.sendPromptError(e,`${this.describeAskUserQuestionRefusal(n).replace(/\.?$/,".")} Your reply (including the follow-up text) was not sent.`)}catch(P){s.warn("sendPromptError on nq_mid option_with_followup failed",{sessionId:e,error:P instanceof Error?P.message:String(P)})}return}if(await new Promise(P=>setTimeout(P,200)),E())return;let k=n.pendingQuestionsQueue?.shift();k?(n.pendingCurrentQuestion=k,n.pendingPanePromptIdentity=this.buildAskUserQuestionPaneIdentity(k,n.activeAskUserQuestionInvocation?.occurrenceId)):s.warn("nq_mid option_with_followup: queue.shift returned undefined unexpectedly",{sessionId:e}),await this.handleMobileReplyAsDismissAndPrompt(e,n,n.pendingPanePromptIdentity,C,S,E);return}if(R==="nq_last"){await this.handleOptionWithFollowupOnLastQ(e,n,y,_,D,C,S,E);return}let ee=await this.answerVerifiedInteractivePrompt(n,y,_,{pressEnter:!1});if(E())return;if(!ee){try{await this.sendPromptError(e,`${this.describeAskUserQuestionRefusal(n).replace(/\.?$/,".")} Your reply was not sent.`)}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(C){let M=await this.sendFreeFormWhenNoChooser(e,n,C,S);if(E())return;M||s.warn("on-SYNTH option_with_followup text-send failed",{sessionId:e})}let N=$,te=D.toLowerCase(),Z;N&&te==="cancel"?Z=C?"\u2192 Cancel \u2014 AskUserQuestion cancelled, sending your reply as a new prompt":"\u2192 Cancel \u2014 AskUserQuestion cancelled, no answers submitted":N&&te.startsWith("submit")?Z=C?"\u2192 Submit answers \u2014 AskUserQuestion completed, sending your reply as a new prompt":"\u2192 Submit answers \u2014 AskUserQuestion completed":Z=C?`\u2192 ${D} + sending your reply as a new prompt`:`\u2192 ${D}`;try{await this.emitUserChoice(e,Z)}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(n);return}let U=v?.[I.option]||I.option;s.info("User selected option with follow-up",{option:I.option,terminalInput:U,followUpText:I.followUpText});let T=await this.answerVerifiedInteractivePrompt(n,y,U);if(E()){await this.markEventExecutedIdempotent(n,t);return}if(T){if(await this.markEventExecutedIdempotent(n,t),E())return;if(!f){s.warn("emitAnswerAck called without promptId \u2014 clearing state + skipping ack",{sessionId:e,source:"option_with_followup",eventId:t.eventId}),this.promoteFingerprintAndClearPromptState(n);return}try{await this.emitAnswerAck(e,`Selected option ${I.option}`,{promptId:f,questionIndex:0,isTerminal:!0})}catch(R){s.warn("[walker] emitAnswerAck (option_with_followup) failed \u2014 continuing to clearPromptState + executeMobilePrompt",{sessionId:e,promptId:f,error:R instanceof Error?R.message:String(R)})}if(E())return;if(this.promoteFingerprintAndClearPromptState(n),I.followUpText){await new Promise(A=>setTimeout(A,1e3));let R={...t,content:I.followUpText};await this.executeMobilePrompt(e,R)}}else try{await this.sendPromptError(e,`${this.describeAskUserQuestionRefusal(n).replace(/\.?$/,".")} Your reply (including the follow-up text) was not sent.`)}catch(R){s.warn("[walker] sendPromptError threw \u2014 relying on idempotent mark in finally",{sessionId:e,eventId:t.eventId,error:String(R)})}finally{await this.markEventExecutedIdempotent(n,t)}}else{if(n.pendingQuestionsQueue!==void 0){let T=this.getWalkerPosition(n);if(s.info("AUQ walker \u2014 send_as_response dispatch",{sessionId:e,walkerPosition:T,contentPreview:w.slice(0,80)}),await this.markEventExecutedIdempotent(n,t),E())return;if(T==="on_synth"){let R=await this.answerVerifiedInteractivePrompt(n,y,"2",{pressEnter:!1});if(E())return;if(!R){try{await this.emitUserChoice(e,`AskUserQuestion was not cancelled \u2014 ${this.describeAskUserQuestionRefusal(n).replace(/\.?$/,".")} Your reply was not sent.`)}catch(O){s.warn("emitUserChoice on send_as_response on-SYNTH Cancel failed",{sessionId:e,error:O instanceof Error?O.message:String(O)})}return}if(await new Promise(O=>setTimeout(O,1500)),E())return;let A=await this.sendFreeFormWhenNoChooser(e,n,w,S);if(E())return;A||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(O){s.warn("emitUserChoice on send_as_response on-SYNTH success failed",{sessionId:e,error:O instanceof Error?O.message:String(O)})}if(E())return;this.promoteFingerprintAndClearPromptState(n);return}await this.handleMobileReplyAsDismissAndPrompt(e,n,y,w,S,E);return}s.info("Rejecting legacy prompt before forwarding free-form mobile input",{responsePreview:w.slice(0,80),attachmentCount:t.attachments?.length??0});let U=await this.dismissLivePromptForConversation(n,d);if(E()){await this.markEventExecutedIdempotent(n,t);return}if(U)await this.clearInteractivePromptsForAcceptedInput(n),await new Promise(T=>setTimeout(T,200)),await this.executeMobilePrompt(e,{...t,content:w});else try{await this.sendPromptError(e,"Failed to dismiss the active prompt; your message was not sent")}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(n,t)}}}finally{b&&n.inFlightPromptIds&&n.inFlightPromptIds.delete(b),m&&n.inFlightEventIds&&n.inFlightEventIds.delete(m)}}async sendInteractivePromptAsync(e,t,i){let n=this.activeSessions.get(e),r=n?.activeAskUserQuestionInvocation?.occurrenceId,o=n?.promptGenerationToken?{...n.promptGenerationToken}:void 0,c=(0,p.prepareEventTimestamp)({orderingKey:e});await new Promise(T=>setTimeout(T,500));let a=process.env.CODEVIBE_TMUX_SESSION,u={...t.metadata||{}},d,b=t.metadata?.tool_name,m=t.metadata?.tool_input,w=b==="AskUserQuestion"&&Array.isArray(m?.questions)?m.questions:[];if(w.length>0&&Array.isArray(w[0]?.options)&&w[0].options.length>0){let T=w[0];d=this.buildAskUserQuestionPaneIdentity(T,r);let R=[];if(a)try{let{exec:_}=await import("child_process"),V=D=>new Promise(($,ee)=>{_(D,{timeout:5e3},(N,te)=>{N?ee(N):$({stdout:te||""})})}),{stdout:L}=await V(`tmux capture-pane -p -e -S -30 -t '${a}'`);R=this.parseAskUserQuestionSynthTail(L),s.info("AskUserQuestion synth-tail parsed from tmux",{tailCount:R.length,tail:R.map(D=>D.label)})}catch(_){s.warn("Failed to capture tmux for AskUserQuestion synth-tail; emitting without synth tail",{error:_ instanceof Error?_.message:String(_)})}else s.info("No tmux session \u2014 AskUserQuestion synth tail will be empty");let A=this.activeSessions.get(e);if(A){let _=A.promptGenerationToken;o&&_?.gen===o.gen?A.pendingSynthTail=R:s.warn("AskUserQuestion synth-tail: stale async \u2014 token gen mismatch, skipping pendingSynthTail write",{tokenAtEmit:o,currentToken:_,sessionId:e})}let O=this.buildQuestionWireData(T,R);u.options=JSON.parse(JSON.stringify(O.options)),u.submitMap=JSON.parse(JSON.stringify(O.submitMap)),u.instructions=O.instructions,u.tool_name="AskUserQuestion",u.tool_input={questions:[T]},i=T.question;let Q=typeof t.prompt_id=="string"&&t.prompt_id.length>0,C=fn(w);if(Q){let _=this.activeSessions.get(e);if(_){let V=_.promptGenerationToken;o&&V?.gen===o.gen?(_.pendingQuestionsQueue=C.remainingQueue,_.pendingCurrentQuestion=T,_.pendingPanePromptIdentity=d,_.hasReviewScreen=C.hasReviewScreen):s.warn("AskUserQuestion: stale async \u2014 token gen mismatch, skipping walker-state write",{tokenAtEmit:o,currentToken:V,sessionId:e})}}else s.warn("AskUserQuestion: empty prompt_id, degrading to single-Q legacy emit",{questionCount:w.length});s.info("AskUserQuestion: emitting Q1 only (Q2..QN queued)",{questionCount:C.questionCount,hasReviewScreen:C.hasReviewScreen,queuedRemaining:Q?C.remainingQueue.length:0,optionCountFirst:O.options.length,questionPreview:T.question.slice(0,80)})}else if(a)try{let{exec:T}=await import("child_process"),R=C=>new Promise((_,V)=>{T(C,{timeout:5e3},(L,D)=>{L?V(L):_({stdout:D||""})})}),{stdout:A}=await R(`tmux capture-pane -p -e -S -30 -t '${a}'`),O=A.split(`
7
7
  `);s.info("tmux capture result",{tmuxSession:a,totalLines:O.length,lastLines:O.slice(-15).map(C=>C.replace(/\x1B[^m]*m/g,"").trim()).filter(Boolean)});let Q=(0,p.parseInteractivePrompt)(A);if(Q&&Q.options.length>0)u.options=Q.options,u.submitMap=Q.submitMap,u.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(T){this.suppressPromptOptionsUnavailable(e,o,`tmux capture failed: ${T instanceof Error?T.message:String(T)}`);return}else{this.suppressPromptOptionsUnavailable(e,o,"no tmux session");return}let f=this.activeSessions.get(e),g=!o||f?.promptGenerationToken?.gen===o.gen;f&&u.submitMap&&g&&(f.pendingSubmitMap=u.submitMap,f.pendingPanePromptIdentity=d);let y=this.encryptForEmit(i,u,p.EventType.INTERACTIVE_PROMPT,e);if(!y)return;let h=y.content,v=y.metadata,I=y.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 F=t.prompt_id?this.e1PromptRaises.get(t.prompt_id):void 0;if(t.prompt_id&&(this.e1NeedsResolution.has(t.prompt_id)||o&&!F))return;let U=F?(0,p.raiseFieldsFromRecord)(F.record):{};F&&this.stashE1RaiseContent(F.record.promptId,i,u),await this.appSyncClient.createEvent({sessionId:e,type:p.EventType.INTERACTIVE_PROMPT,source:t.source,content:h,metadata:v,promptId:t.prompt_id,...U,timestamp:c,isEncrypted:I?!0:void 0}),s.info("Interactive prompt sent to AppSync with dynamic options",{sessionId:e})}buildQuestionWireData(e,t=[]){let i=(e.options||[]).map((a,u)=>{let d=typeof a=="string",b=d?a:a.label||"",m=d?"":a.description||"",w=d?"":a.preview||"",f={number:String(u+1),text:b};return m&&(f.description=m),w&&(f.preview=w),f});if(!e._isSubmit)for(let a of t)i.push({number:String(i.length+1),text:a.label});let n=Object.fromEntries(i.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:i,submitMap:n,instructions:c}}parseAskUserQuestionSynthTail(e){let t=(0,p.normalizeSnapshot)(e);if(!t)return[];let i=t.split(`
8
8
  `).slice(-14),n=[],r=new Set,o=/^\s*(?:[›❯▸▶➜➤]\s*)?(?:\d+\.\s+)?(Type something|Chat about this)\.?\s*$/i;for(let c of i){let a=c.match(o);if(!a)continue;let d=a[1].toLowerCase()==="type something"?"Type something":"Chat about this";r.has(d)||(n.push({label:d}),r.add(d))}return n}suppressPromptOptionsUnavailable(e,t,i){s.warn("Interactive prompt: real options unavailable \u2014 suppressing mobile prompt (no fabricated options)",{sessionId:e,reason:i});let n=t?.promptId,r=n?this.e1PromptRaises.get(n):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 i=this.e1PromptRaises.get(t.promptId);if(!i||i.record!==t||this.e1NeedsResolution.has(t.promptId))return!1;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.",r=this.encryptForEmit(n,{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:n,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(i=>i.number).join(", ")}. Append a message to provide alternative instructions.`}parseInteractivePromptInput(e,t){return Ot(e,t)}async markEventExecuted(e){try{return await this.writeEventStatus(e,p.DeliveryStatus.EXECUTED),s.info("Event marked as EXECUTED",{eventId:e.eventId}),!0}catch(t){if(!String(t).includes("INVALID_DELIVERY_STATUS_TRANSITION"))return s.warn("Failed to mark event as EXECUTED",{eventId:e.eventId,error:t}),!1;s.warn("EXECUTED mark rejected by the delivery-status rule; repairing SENT\u2192DELIVERED first",{eventId:e.eventId})}try{await this.writeEventStatus(e,p.DeliveryStatus.DELIVERED)}catch(t){return s.warn("DELIVERED repair failed; the event keeps its current status",{eventId:e.eventId,error:String(t)}),!1}try{return await this.writeEventStatus(e,p.DeliveryStatus.EXECUTED),s.info("Event marked as EXECUTED",{eventId:e.eventId,repaired:!0}),!0}catch(t){return s.warn("Failed to mark event as EXECUTED after the DELIVERED repair",{eventId:e.eventId,error:t}),!1}}async writeEventStatus(e,t){await this.appSyncClient.updateEventStatus({eventId:e.eventId,sessionId:e.sessionId,timestamp:e.timestamp,deliveryStatus:t})}async sendPromptError(e,t){if(de("fail-notification"))throw s.warn("DEV FAULT fail-notification: simulating a failed NOTIFICATION createEvent",{sessionId:e}),new Error("DEV FAULT: simulated notification failure");let i={error:!0},n=this.encryptForEmit(t,i,p.EventType.NOTIFICATION,e);if(!n)return!1;let r=n.content,o=n.metadata,c=n.isEncrypted;return 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}),!0}async emitUserChoice(e,t){let i=this.encryptForEmit(t,{source:"codevibe_v2_user_choice"},p.EventType.ASSISTANT_RESPONSE,e);if(!i)return;let n=i.content,r=i.metadata,o=i.isEncrypted;await this.appSyncClient.createEvent({sessionId:e,type:p.EventType.ASSISTANT_RESPONSE,source:p.EventSource.DESKTOP,content:n,metadata:r,timestamp:(0,p.prepareEventTimestamp)({orderingKey:e}),isEncrypted:o?!0:void 0})}async emitAnswerAck(e,t,i){let n={promptAnswered:!0,...i},r=this.encryptForEmit(t,n,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(i=>`${i.number}:${this.normalizePromptIdentityText(i.text)}`)].join("::");return{kind:"parsed",key:(0,ae.createHash)("sha256").update(t).digest("hex")}}buildAskUserQuestionPaneIdentity(e,t){let i=this.normalizePromptIdentityText(e.question??""),n=(e.options??[]).map(o=>this.normalizePromptIdentityText(typeof o=="string"?o:[o.label,o.description,o.preview].filter(Boolean).join(" "))),r=["ask_user_question",i,...n].join("::");return{kind:"ask_user_question",key:(0,ae.createHash)("sha256").update(r).digest("hex"),questionText:i,optionLabels:n,hookOccurrenceId:t}}async captureLivePromptPane(e=40){let t=process.env.CODEVIBE_TMUX_SESSION;if(!t)throw new Error("CODEVIBE_TMUX_SESSION is unset");let i=t.replace(/'/g,"'\\''"),n=Number.isInteger(e)&&e>0?e:40,{stdout:r}=await Oe(`tmux capture-pane -p -e -S -${n} -t '${i}'`,{timeout:3e3});return r||""}liveAskUserQuestionMatches(e,t){let i=(0,p.normalizeSnapshot)(e).split(`
9
9
  `).map(g=>g.trim()).filter(Boolean);if(i.length===0||!t.questionText||!t.optionLabels)return!1;let n=/(?: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=i.length-1,o=-1;for(let g=r;g>=Math.max(0,r-3);g-=1)if(n.test(i[g])){o=g;break}if(o<0)return!1;let c=/^(?:[›❯▸▶➜➤]\s*)?(\d+)\.\s+(.+)$/,a=[];for(let g=Math.max(0,o-30);g<o;g+=1){let y=i[g].match(c);y&&a.push({index:g,number:Number(y[1]),firstLine:y[2]})}let u=[...a].reverse().find(g=>g.number===1);if(!u)return!1;let d=a.filter(g=>g.index>=u.index);if(d.length!==t.optionLabels.length||d.some((g,y)=>g.number!==y+1)||d.map((g,y)=>{let h=d[y+1]?.index??o,v=[g.firstLine,...i.slice(g.index+1,h).filter(I=>!n.test(I)&&!/^[─━-]+$/.test(I))];return this.normalizePromptIdentityText(v.join(" "))}).some((g,y)=>g!==t.optionLabels[y]))return!1;let m=d[0].index,w=Math.max(0,m-14);for(let g=m-1;g>=w;g-=1)if(n.test(i[g])){w=g+1;break}let f=this.normalizePromptIdentityText(i.slice(w,m).join(" "));return f===t.questionText||f.endsWith(` ${t.questionText}`)}livePromptHasNegativeAffordance(e){return(0,p.normalizeSnapshot)(e).split(`
package/hooks/common.sh CHANGED
@@ -397,6 +397,28 @@ _codevibe_ensure_lock_cleanup() {
397
397
  CODEVIBE_ENSURE_SESSION_ID=""
398
398
  }
399
399
 
400
+ # Exit code a daemon uses when it cannot read its stored credentials
401
+ # (core's CREDENTIAL_STORAGE_UNAVAILABLE_EXIT_CODE). Signing in again does not fix it.
402
+ CODEVIBE_CREDENTIAL_STORAGE_EXIT=78
403
+
404
+ # The user-facing SessionStart notice for a daemon that could not start. $1 is
405
+ # ensure_mcp_daemon's return code; 78 prints the daemon's own explanation (cause and
406
+ # fix) from the daemon log instead of the sign-in hint.
407
+ print_mcp_daemon_start_failure() {
408
+ local rc="$1"
409
+ local mcp_log="${CODEVIBE_TMPDIR}/codevibe-claude-mcp.log"
410
+ echo ""
411
+ if [ "$rc" = "$CODEVIBE_CREDENTIAL_STORAGE_EXIT" ]; then
412
+ echo "⚠️ CodeVibe: mobile sync is disabled for this session."
413
+ awk '/CodeVibe could not read its stored credentials/ { block = ""; on = 1 } on && !/^\[/ { block = block $0 "\n" } END { printf "%s", block }' "$mcp_log" 2>/dev/null | sed 's/^/ /'
414
+ echo " (Full log: $mcp_log)"
415
+ else
416
+ echo "⚠️ CodeVibe: Not authenticated or the sync daemon failed to start. Mobile sync may be disabled."
417
+ echo " Run 'codevibe login' to sign in and enable mobile sync."
418
+ fi
419
+ echo ""
420
+ }
421
+
400
422
  ensure_mcp_daemon() {
401
423
  local session_id="$1"
402
424
  # (#638 H2) Optional: a port KNOWN-BAD for /event delivery (send_to_mcp's POST
@@ -665,7 +687,16 @@ ensure_mcp_daemon() {
665
687
  CODEVIBE_ENSURE_CHILD_PID=""
666
688
  rc=0
667
689
  elif ! ps -p "$server_pid" > /dev/null 2>&1; then
668
- log "ERROR" "ensure_mcp_daemon: MCP server died on startup (likely auth failure). Run 'codevibe login' first."
690
+ # The daemon is our child: collect its exit code to tell a credential-store
691
+ # failure (not fixed by signing in) from the usual not-signed-in death.
692
+ local death_rc=0
693
+ wait "$server_pid" 2>/dev/null || death_rc=$?
694
+ if [ "$death_rc" = "$CODEVIBE_CREDENTIAL_STORAGE_EXIT" ]; then
695
+ log "ERROR" "ensure_mcp_daemon: MCP server could not read its stored credentials (exit $death_rc) — not a sign-in problem; cause and fix are in ${CODEVIBE_TMPDIR}/codevibe-claude-mcp.log"
696
+ rc=$CODEVIBE_CREDENTIAL_STORAGE_EXIT
697
+ else
698
+ log "ERROR" "ensure_mcp_daemon: MCP server died on startup (exit $death_rc; usually not signed in — run 'codevibe login')."
699
+ fi
669
700
  else
670
701
  # (#638 F3) Cold-start cap elapsed and the port never appeared, but the launched
671
702
  # PID is STILL ALIVE. Leaving it alive-and-UNMAPPED is the exact #638 duplicate
@@ -409,14 +409,11 @@ if [ -n "$EXISTING_SERVER_PORT" ]; then
409
409
  # path would call ensure_mcp_daemon AGAIN (a second launch attempt in the
410
410
  # same invocation). The NEXT hook event's send_to_mcp provides the retry.
411
411
  log "WARN" "Failed to send SessionStart event after relaunch (exit: $CURL_EXIT, HTTP $HTTP_CODE) — giving up for this invocation (#638 I1/G7)"
412
- log "WARN" "Run 'codevibe login' to enable mobile sync"
412
+ log "WARN" "Mobile sync unavailable for this event; see ${CODEVIBE_TMPDIR}/codevibe-claude-mcp.log"
413
413
  else
414
- # Mirror the plain-ensure failure messaging below (auth-death is the
415
- # common cause); same G7 rule — no send_to_mcp after a failed ensure.
416
- echo ""
417
- echo "⚠️ CodeVibe: Not authenticated or the sync daemon failed to start. Mobile sync may be disabled."
418
- echo " Run 'codevibe login' to sign in and enable mobile sync."
419
- echo ""
414
+ # Mirror the plain-ensure failure messaging below; same G7 rule — no
415
+ # send_to_mcp after a failed ensure.
416
+ print_mcp_daemon_start_failure "$?"
420
417
  log "WARN" "SessionStart: exclude-port ensure failed after transport failure — skipping send_to_mcp (one launch attempt per hook; next hook event retries) (#638 I1/G7)"
421
418
  fi
422
419
  exit 0
@@ -441,13 +438,9 @@ fi
441
438
  if PORT=$(ensure_mcp_daemon "$SESSION_ID"); then
442
439
  log "INFO" "MCP daemon ready on port $PORT for session $SESSION_ID"
443
440
  else
444
- # ensure_mcp_daemon already logged the specific failure (process died on
445
- # startup = likely auth failure, vs. slow start). Surface the user-facing auth
446
- # hint, matching the prior behavior for the auth-death case.
447
- echo ""
448
- echo "⚠️ CodeVibe: Not authenticated or the sync daemon failed to start. Mobile sync may be disabled."
449
- echo " Run 'codevibe login' to sign in and enable mobile sync."
450
- echo ""
441
+ # ensure_mcp_daemon already logged the specific failure. Its return code says
442
+ # which notice is true: 78 = the daemon could not read its stored credentials.
443
+ print_mcp_daemon_start_failure "$?"
451
444
  # (#638 G7) Do NOT fall through to send_to_mcp after a FAILED ensure: send_to_mcp's
452
445
  # transport-failure path calls ensure_mcp_daemon AGAIN, making this hook perform a
453
446
  # SECOND full launch attempt in the same invocation (wasteful under the instance
@@ -467,7 +460,7 @@ if [ $EXIT_CODE -eq 0 ]; then
467
460
  log "INFO" "SessionStart event sent successfully"
468
461
  else
469
462
  log "WARN" "Failed to send SessionStart event (exit code: $EXIT_CODE)"
470
- log "WARN" "Run 'codevibe login' to enable mobile sync"
463
+ log "WARN" "Mobile sync unavailable for this event; see ${CODEVIBE_TMPDIR}/codevibe-claude-mcp.log"
471
464
  fi
472
465
 
473
466
  exit 0
@@ -489,6 +489,22 @@ export declare function shouldDeliverSubscribedEvent(event: Pick<Event, 'source'
489
489
  * failure from a genuine sign-in rejection.
490
490
  */
491
491
  export type AuthFailureKind = 'no_tokens' | 'refresh_auth_rejected' | 'refresh_network' | 'credential_storage_unavailable';
492
+ /**
493
+ * Exit code a companion daemon uses when it cannot read its stored credentials.
494
+ * The plugins' hooks and launchers key their user-facing message off it, so a
495
+ * credential-store failure is never reported as "run codevibe login".
496
+ */
497
+ export declare const CREDENTIAL_STORAGE_UNAVAILABLE_EXIT_CODE = 78;
498
+ export interface StoredAuthFailureReport {
499
+ exitCode: number;
500
+ message: string;
501
+ }
502
+ /**
503
+ * What a companion daemon prints, and exits with, when
504
+ * `authenticateWithStoredTokens()` fails. Signing in again repairs only a missing
505
+ * or rejected sign-in; for the other kinds it says what actually went wrong.
506
+ */
507
+ export declare function describeStoredAuthFailure(kind: AuthFailureKind | null, detail: string | null): StoredAuthFailureReport;
492
508
  export declare const DEFAULT_HEARTBEAT_INTERVAL_MS = 120000;
493
509
  export declare const MIN_HEARTBEAT_INTERVAL_MS = 1000;
494
510
  export declare const MAX_HEARTBEAT_INTERVAL_MS = 300000;
@@ -530,6 +546,11 @@ export declare class AppSyncClient {
530
546
  * round-1 Codex M1.
531
547
  */
532
548
  private lastAuthFailureKind;
549
+ /**
550
+ * The credential-store error message behind a `credential_storage_unavailable`
551
+ * failure (it names the cause and the fix). `null` for every other outcome.
552
+ */
553
+ private lastAuthFailureDetail;
533
554
  /**
534
555
  * Sentinel set inside `performRefresh` / `callCognitoRefresh` when
535
556
  * the refresh round-trip fails with a network-shaped error (DNS,
@@ -592,6 +613,8 @@ export declare class AppSyncClient {
592
613
  * cannot repair a locked or unavailable keyring.
593
614
  */
594
615
  getLastAuthFailureKind(): AuthFailureKind | null;
616
+ /** The credential-store error message behind a `credential_storage_unavailable` failure. */
617
+ getLastAuthFailureDetail(): string | null;
595
618
  /**
596
619
  * Heuristic — does the error message look like a transient network
597
620
  * failure rather than an auth-token rejection? Used for refresh-path
@@ -945,6 +968,7 @@ export declare class AppSyncClient {
945
968
  generatePlannerAdvisory(input: {
946
969
  sessionId: string;
947
970
  prompt: string;
971
+ images?: string[];
948
972
  maxTokens?: number;
949
973
  temperature?: number;
950
974
  }): Promise<{
@@ -1,4 +1,5 @@
1
- export { AppSyncClient, AppSyncAuthenticationError, AppSyncGraphQLError, DownloadUrlResponse, resolveHeartbeatIntervalMs, DEFAULT_HEARTBEAT_INTERVAL_MS, MIN_HEARTBEAT_INTERVAL_MS, MAX_HEARTBEAT_INTERVAL_MS, isRecognizedNonProductionEnvironment, RECOGNIZED_NON_PRODUCTION_ENVIRONMENTS, } from './appsync-client';
1
+ export { AppSyncClient, AppSyncAuthenticationError, AppSyncGraphQLError, DownloadUrlResponse, resolveHeartbeatIntervalMs, DEFAULT_HEARTBEAT_INTERVAL_MS, MIN_HEARTBEAT_INTERVAL_MS, MAX_HEARTBEAT_INTERVAL_MS, isRecognizedNonProductionEnvironment, RECOGNIZED_NON_PRODUCTION_ENVIRONMENTS, CREDENTIAL_STORAGE_UNAVAILABLE_EXIT_CODE, describeStoredAuthFailure, } from './appsync-client';
2
+ export type { AuthFailureKind, StoredAuthFailureReport } from './appsync-client';
2
3
  export { coerceAwsJsonObject } from './appsync-client';
3
4
  export type { ClassBPacketEnvelopeMeta } from './appsync-client';
4
5
  export type { TaskReviewSummary, TaskReviewSummaryRound, TaskReviewSummaryReviewer, TaskGroupStatusResult, PutContextItemInput, } from './appsync-client';