grix-connector 4.11.6 → 4.11.7

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,11 +1,11 @@
1
- import{spawn as se,spawnSync as ne,execSync as re,execFile as ct}from"node:child_process";import{promisify as dt}from"node:util";import{randomUUID as J}from"node:crypto";import{killProcessGroup as q,hasChildProcesses as ut,resolveCommandPath as ht,resolveWindowsExecInvocation as ye}from"../../core/runtime/spawn.js";import{chmodSync as ae,readdirSync as oe,readFileSync as B,realpathSync as we,rmSync as ft,statSync as I,truncateSync as pt,existsSync as S,openSync as mt,writeSync as vt,closeSync as gt,renameSync as Pe,mkdirSync as Ce,constants as Te,watch as Et}from"node:fs";import{appendFile as _t,mkdir as L,readFile as T,rm as Ae,stat as Ie,writeFile as D}from"node:fs/promises";import{dirname as St,isAbsolute as yt,join as f,resolve as wt}from"node:path";import{homedir as k,tmpdir as Pt}from"node:os";import le from"node:net";import{EventEmitter as be}from"node:events";import{fileURLToPath as Ct}from"node:url";let Y=null;if(process.platform==="win32")try{Y=await import("node-pty")}catch{Y=null}import{resolveRuntimePaths as y}from"../../core/config/index.js";import{normalizeClaudeEffort as Tt,normalizeClaudeEnvEffort as At}from"../../core/config/claude-direct-env.js";import{SESSION_MODE_IDS as z}from"./protocol-contract.js";import{buildSessionContextBlock as It}from"../shared/session-context.js";import{log as o}from"../../core/log/index.js";import{ActivityStatusManager as bt}from"./activity-status-manager.js";import{HookSignalStore as kt}from"../../core/hooks/hook-signal-store.js";import{QuestionStore as ke}from"../../core/persistence/question-store.js";import{PermissionStore as G}from"../../core/persistence/permission-store.js";import{InternalApiServer as Rt}from"../../core/mcp/internal-api-server.js";import{executeEventTool as Re,isEventTool as Dt}from"../../core/mcp/event-tool-executor.js";import{validateToolArgs as $t}from"../../core/mcp/tool-schemas.js";import{isGrixInternalToolName as Ot,normalizeEventToolArgs as xt}from"../../core/mcp/tools.js";import{scanSkills as Lt}from"./skill-scanner.js";import{syncDefaultSkillsToDir as Mt}from"../../default-skills/index.js";import{clearSessionJsonlDriftCache as Ft,countPendingBackgroundTasks as Nt,noteSessionJsonlPath as jt,extractLastAssistantEntry as W,extractTextBlocksAfterOffset as De,hasTerminalToolResultAfterOffset as $e,probeSessionTurnState as ce,resolveSessionJsonlPath as O}from"./usage-parser.js";import{readSettingsEnv as Bt}from"./model-list.js";import{resolveCliPath as Ut,getCliVersion as Ht}from"../../core/util/cli-probe.js";import{GRIX_RELAY_ANTHROPIC_API_KEY as Oe}from"../../core/proxy/index.js";const M="grix",K={developmentChannels:!0,maxTurns:!0};function qt(d){if(!Array.isArray(d))return;const e=d.map(t=>{if(typeof t=="string")return t.trim();if(!t||typeof t!="object")return"";const i=t,s=String(i.label??"").trim();if(s)return s;const n=String(i.value??"").trim();if(n)return n;const r=String(i.text??"").trim();return r||""}).filter(t=>t.length>0);return e.length>0?e:void 0}function xe(d){let e;try{e=JSON.parse(d)}catch{return null}const t=e.questions;if(!Array.isArray(t)||t.length===0)return null;const i=[];for(let s=0;s<t.length;s++){const n=t[s];if(!n||typeof n!="object")continue;const r=n,a=`Question ${s+1}`,l=String(r.header??r.question??a).trim()||a,c=String(r.prompt??r.question??"").trim();if(!c)continue;const u=qt(r.options),h=r.multiSelect===!0||r.multi_select===!0;i.push({header:l,prompt:c,...u?{options:u}:{},...h?{multi_select:!0}:{}})}return i.length>0?i:null}function zt(d){if(!d||typeof d!="string")return null;try{const e=JSON.parse(d);return(typeof e.plan=="string"?e.plan.trim():"")||null}catch{return null}}const Gt=[/Please run \/login/,/API Error:\s*401/,/authentication_error/,/OAuth token has expired/],Wt=[/API Error:\s*401/,/authentication_error/],Jt=[/You're out of extra usage/i,/Stop and wait for limit to reset/i,/Add funds to continue with extra usage/i],Yt=/API Error:\s*400.*(server_tool_use\.id|messages\.\d+\.content)/;function de(d){const e=d?.trim();return e&&!e.startsWith("API Error:")?e:"Claude hit an API format error. Please start a new conversation (/grix open <dir>)."}const Le=300*1e3,Me=300*1e3,Fe=Me+30*1e3,Ne=3e3,Kt=50,Vt=1800*1e3,Qt=["CLAUDE_CODE_ENABLE_TELEMETRY","CLAUDE_CODE_ENHANCED_TELEMETRY_BETA","OTEL_LOGS_EXPORTER","OTEL_TRACES_EXPORTER","OTEL_METRICS_EXPORTER","OTEL_EXPORTER_OTLP_PROTOCOL","OTEL_EXPORTER_OTLP_ENDPOINT","OTEL_EXPORTER_OTLP_HEADERS","OTEL_LOGS_EXPORT_INTERVAL","OTEL_TRACES_EXPORT_INTERVAL","OTEL_LOG_USER_PROMPTS","OTEL_LOG_ASSISTANT_RESPONSES","OTEL_LOG_TOOL_DETAILS","OTEL_LOG_TOOL_CONTENT","OTEL_LOG_RAW_API_BODIES"];function V(d=process.env){const e={...d},t=new Set(Qt.map(i=>i.toLowerCase()));for(const i of Object.keys(e))t.has(i.toLowerCase())&&delete e[i];return e}const je=60*1e3,Be="lost injection respawn",Ue=600*1e3,Xt=250;function He(d,e){const t=Number(process.env[d]);return Number.isFinite(t)&&t>0?t:e}function qe(d){return d.trim().replace(/[^a-zA-Z0-9._-]+/gu,"_")||"unknown"}function Zt(d){const e=ht("expect",d);return yt(e)?e:null}function Q(d){try{return S(d)?I(d).size:0}catch{return 0}}function ze(d){const e=[],t=i=>{let s;try{s=oe(i,{withFileTypes:!0})}catch{return}for(const n of s){const r=f(i,n.name);n.isDirectory()?t(r):n.isFile()&&e.push(r)}};return t(d),e.sort((i,s)=>Ge(i)-Ge(s)||i.localeCompare(s))}function Ge(d){try{return I(d).mtimeMs}catch{return 0}}const We=He("GRIX_RESCUE_SETTLE_POLL_MS",2e3),Je=He("GRIX_RESCUE_SETTLE_BUDGET_MS",12e4),ei=300*1e3,ti="The model's tool call could not be parsed (retry also failed).",ii="[\u7CFB\u7EDF\u81EA\u52A8\u91CD\u8BD5] \u4F60\u4E0A\u4E00\u6761\u56DE\u590D\u7684\u5DE5\u5177\u8C03\u7528\u89E3\u6790\u5931\u8D25\uFF0C\u6CA1\u6709\u53D1\u51FA\u53BB\u3002\u8BF7\u4E0D\u8981\u91CD\u590D\u6267\u884C\u5DF2\u7ECF\u5B8C\u6210\u7684\u64CD\u4F5C\uFF0C\u76F4\u63A5\u91CD\u65B0\u8C03\u7528 reply \u5DE5\u5177\uFF0C\u628A\u4F60\u4E0A\u4E00\u6761\u60F3\u53D1\u7ED9\u7528\u6237\u7684\u7ED3\u8BBA\u539F\u6837\u91CD\u65B0\u53D1\u9001\u4E00\u6B21\u3002",si="Sorry, the previous reply could not be generated properly (the model's tool call failed to parse). Please send it again, and use /grix restart to restart the session if needed.",ni=2*1e3,ue=90*1e3,Ye=1800*1e3,Ke=15*1e3,ri=60*1e3,Ve=10*1e3,ai=30*1e3,Qe=30*1e3,oi=2e3,li=3e4;function ci(d){const e=d.tool_name||"";if(e==="mcp__grix__grix_dispatch_agent")return"\u540E\u53F0\u534F\u8C03\u5B50 agent";if(e.startsWith("mcp__grix__"))return"\u540E\u53F0\u8C03\u7528 Grix \u5DE5\u5177";if(e.startsWith("mcp__"))return"\u540E\u53F0\u8C03\u7528\u5DE5\u5177";switch(e){case"Bash":return"\u540E\u53F0\u6267\u884C\u547D\u4EE4";case"Read":case"Edit":case"Write":case"NotebookEdit":return"\u540E\u53F0\u8BFB\u5199\u6587\u4EF6";case"Task":return"\u540E\u53F0\u8FD0\u884C\u5B50\u4EFB\u52A1";default:return e?`\u540E\u53F0\u8FD0\u884C\uFF1A${e}`:"\u540E\u53F0\u4EFB\u52A1\u8FDB\u884C\u4E2D"}}function di(d){try{return process.kill(d,0),!0}catch(e){return e.code==="EPERM"}}const ui=12e4,hi=3e4,fi=6e4,pi=3e3,X=1e3,Xe=45e3,mi=18e3,he=8192,vi=1e3,gi=2e3,Ze=300,Ei=3e4,_i=2e3,Si=2e3,yi=/(do you want to proceed\?[\s\S]*?\d\.\s*yes)|(only you can approve)/i,wi=/(do you want to proceed)|(only you can approve)|(\d\.\s*yes)|(\d\.\s*no)|(esc to cancel)|(tab to amend)/i,Pi=/^[✳✶✻✽✵❋✺✴❈❖✦✧✢◉◎⬥⬦◇◆▸▹►▻→←↑↓⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏·…\d]*still\s+thinking/i,fe="__grix_key__ ";let et=!1;const pe=new Set;async function Ci(d=[]){const e=new Set(d.filter(t=>Number.isInteger(t)&&t>0));for(let t=0;t<20;t++){const i=await new Promise((s,n)=>{const r=le.createServer();r.once("error",n),r.listen(0,"127.0.0.1",()=>{const a=r.address(),l=typeof a=="object"&&a?a.port:0;r.close(c=>{if(c){n(c);return}s(l)})})});if(i>0&&!e.has(i)&&!pe.has(i))return pe.add(i),i}throw new Error("\u65E0\u6CD5\u5206\u914D MCP \u901A\u77E5\u7AEF\u53E3")}function Ti(d){d>0&&pe.delete(d)}const Ai=["You are connected to a chat via the grix-claude MCP server.",'Messages arrive as <channel source="grix-claude" chat_id="..." event_id="..." message_id="..." user_id="...">text</channel>.',"When present, the channel metadata includes context_messages_json \u2014 a JSON array of earlier visible messages for this trigger; each entry carries sender_id and content. An entry whose content starts with [\u5F15\u7528\u6D88\u606F] is the quoted (replied-to) message being responded to. Use these records as context (prefer them over inferring from quoted_message_id alone), in group chats use sender_id to tell who said what, and only answer the current message.","Local transcript availability, session binding, recovery mode, and connector history-loss diagnostics are internal operational details. Do not mention them, ask the user to choose a recovery mode, or say that history/context was lost unless the user explicitly asks to debug that mechanism.",'Both your plain text and the reply tool are delivered to the user. You are talking directly to the user, so address them in the second person ("you"); do not slip into third-person narration about them (e.g. "\u5DF2\u7ECF\u628A\u7ED3\u8BBA\u56DE\u590D\u7ED9\u8001\u90ED\u4E86") \u2014 say it straight to them ("\u7ED3\u8BBA\u662F\u2026\u2026").',"While you work, use plain text for short progress notes. Deliver your final conclusion \u2014 the answer the user is waiting for \u2014 through the reply tool; that is the message the user treats as your result and the one the connector quotes. Your plain text reaches the user too \u2014 it is not private scratch space, so it is a real channel for progress and for anything the user needs mid-turn; you just need not restate the full conclusion as plain text once it is in the reply. The connector automatically quotes the message you are answering, so you normally do not need to set reply_to (set it only to quote a different earlier message).","Make the reply tool call the last thing you do in a turn and put your whole conclusion inside it: once you have called reply, any further text you write in the same turn is dropped, not delivered. Progress notes go before the reply; the reply ends the turn.",'Never explain the delivery mechanics to the user or tell them you "already sent it via the reply tool" \u2014 that is internal plumbing they neither see nor care about. If the user says they did not receive or cannot read something, do not insist you already sent it; assume the delivery or the format failed and send it again, switching format when that is the likely cause (e.g. render a diagram to an image, strip code fences, upload a file).',"If you intentionally do not want to send any visible message, call the complete tool with event_id and a final status.",'Do not send a bare acknowledgement or "got it / on it / starting now" message before doing the work; do the work first, then deliver the actual result through the reply tool. During a long-running task, write a short plain-text progress note when you reach a meaningful milestone so the user can see progress as it happens.',"You only run while handling a turn; once your turn ends you are frozen and cannot send anything until the user messages you again.",'Unbreakable rule: never end a turn having promised something you cannot deliver. In particular, never start a task, detach it (nohup, a trailing &, any background process or watcher), end the turn, and promise to "notify when it finishes" \u2014 that notification can never happen, so do not promise it.',"For any task whose result the user is waiting on (deploy, build, rollout, tests), run it in the foreground and wait for it to finish within this same turn, then report the real outcome. You may launch several in the background at once for parallelism, but you must still collect all of their results within this same turn before you reply and finish.","Two cases where leaving a process running is correct: a long-lived service that is meant to keep running (dev server, daemon) \u2014 background it, then report that it started plus how to check or stop it; and a task too long to finish in one turn \u2014 you may leave it running, but state plainly that you cannot auto-notify and give the user a concrete way to retrieve the result later (a log path, a PID, or asking them to ping you to re-check)."];function Ii(d,e,t){const i=[...Ai];e&&i.unshift(It(e,t));try{const s=d?.agentName?.trim().slice(0,200),n=d?.introduction?.trim().slice(0,2e3);if(s||n){const r=[s?`Your name is "${s}".`:"",n?`Your self-introduction: ${n}`:""].filter(Boolean).join(" ");i.unshift(r)}}catch{}return i.join(" ")}function tt(d){return String(d??"").trim().toLowerCase()===z.approval?z.approval:z.fullAuto}class Ss extends be{type="claude";config;bridgeCallbacks;mcpServerProcess=null;internalApi=null;claudeProcess=null;claudePty=null;singleTurnFallbackProcesses=new WeakSet;singleTurnFallbackPtys=new WeakSet;lostInjectionKilledProcesses=new WeakSet;lostInjectionKilledPtys=new WeakSet;singleTurnFallbackChild=null;spawnPromise=null;lifecycleVersion=0;sessionId="";alive=!1;stopped=!1;activeEvent=null;completedAuditBoundaries=new Map;activeEventIdleTimer=null;activeEventHardTimer=null;activeEventPostReplyTimer=null;activeEventPostReplyWatcher=null;activeEventPostReplyPoll=null;rescueSettleTimer=null;stopHookBarrierSessionId=null;stopHookBarrierTimer=null;selfDrivenActive=!1;selfDrivenLastSignalAt=0;selfDrivenSweepTimer=null;selfDrivenReported=!1;selfDrivenReportTimer=null;selfDrivenLabel=null;selfDrivenTextOffset=null;selfDrivenDeliverTimer=null;selfDrivenReplied=!1;compacting=!1;compactingTimer=null;compactionDoneResolver=null;mcpServerReady=!1;mcpStartupFailureHandled=!1;mcpChannelBroken=!1;channelGateClosed=!1;startupChannelListening=!1;startupChannelListeningAt=0;startupDialogSeenAt=0;windowsTrustDialogAnswered=!1;windowsTrustDialogTimer=null;devChannelsConfirmTimer=null;ptyDialogBusy=!1;pendingMcpFailureTimer=null;sessionIdConflictDetected=!1;sessionIdConflictRetriedEventIds=new Set;malformedToolRetriedEventIds=new Set;lostInjectionRetriedEventIds=new Set;runtimeResolver=null;activityManager=null;deferredModelId=null;claudeCliCapabilities=null;claudeChildPid=0;claudeCliSessionId="";claudeSessionCwd="";claudeMcpConfigPath="";expectRunDir="";auditEvidencePaths=null;sessionState=null;authFailureUntil=0;usageLimitUntil=0;completedEventIds=new Map;pendingQuestion=new Map;pendingPermissions=new Map;lastClearedEvent=null;textForwardSeq=0;constructor(e,t){super(),this.config=e,this.bridgeCallbacks=t;const s=(e.options??{}).sessionRuntimeResolver;typeof s=="function"&&(this.runtimeResolver=s)}async start(){this.lifecycleVersion+=1,this.stopped=!1,this.alive=!0,this.internalApi=new Rt,this.internalApi.setInvokeHandler(async(e,t,i,s)=>this.handleInternalInvoke(e,t,i,s)),this.internalApi.setStatusLineHandler(async e=>{this.handleStatusLineUpdate(e)}),await this.internalApi.start(0),this.internalApi.setMcpBridgeUpHandler(e=>this.onMcpBridgeUp(e)),this.internalApiPort=parseInt(new URL(this.internalApi.baseUrl).port,10),this.notifyPort=0,o.info("claude-adapter",`Adapter started (stdio MCP mode, internal API at ${this.internalApi.baseUrl})`)}onMcpBridgeUp(e){this.bridgeCallbacks.sendMcpFrame?.(e)}deliverMcpFrameToAgent(e){this.internalApi?.sendMcpFrameToBridge(e)}async stop(){if(o.info("claude-adapter",`Stopping adapter (sessionId=${this.sessionId}, alive=${this.alive}, activeEvent=${this.activeEvent?.eventId??"none"})`),this.lifecycleVersion+=1,this.stopped=!0,this.alive=!1,this.activeEvent&&(this.bridgeCallbacks.sendEventResult(this.activeEvent.eventId,"canceled","adapter stopped"),this.clearActiveEvent()),this.stopComposing(),this.claudeCliSessionId&&Ft(this.claudeCliSessionId),this.stopSelfDriven(),this.clearSelfDrivenDeliverTimer(),this.clearStopHookBarrier(),this.compacting=!1,this.compactingTimer&&(clearTimeout(this.compactingTimer),this.compactingTimer=null),this.compactionDoneResolver&&(this.compactionDoneResolver("stopped"),this.compactionDoneResolver=null),this.mcpChannelBroken=!1,this.channelGateClosed=!1,this.startupChannelListening=!1,this.startupChannelListeningAt=0,this.startupDialogSeenAt=0,this.deferredModelId=null,this.clearPendingMcpFailureTimer(),this.clearWindowsTrustDialogTimer(),this.windowsTrustDialogAnswered=!1,this.clearDevChannelsConfirmTimer(),this.ptyDialogBusy=!1,this.terminalPermissionKeyTimer&&(clearTimeout(this.terminalPermissionKeyTimer),this.terminalPermissionKeyTimer=null),this.terminalPermissionPromptTail="",this.terminalPermissionPromptVisible=!1,this.pendingPermissions.size>0){const s=y(),n=new G(s.permissionRequestsDir);for(const[r]of this.pendingPermissions)n.resolveRequest(r,"deny").catch(()=>{});this.pendingPermissions.clear()}if(this.activityManager&&(this.activityManager.stop(),this.activityManager=null),this.sessionId){const s=this.resolveHookSignalsPath();Ae(s).catch(()=>{})}this.stopMcpServer();const e=this.claudeChildPid;this.claudeChildPid=0;const t=this.claudeProcess,i=this.claudePty;if(this.claudeProcess=null,this.claudePty=null,this.spawnPromise=null,i)try{i.kill()}catch{}if(e>0)try{process.kill(e,"SIGTERM")}catch{}if(t?.pid&&(q(t,"SIGTERM"),!await Promise.race([new Promise(n=>{t.once("exit",()=>n(!0))}),new Promise(n=>{setTimeout(()=>n(!1),5e3)})]))){if(e>0)try{process.kill(e,"SIGKILL")}catch{}q(t,"SIGKILL")}if(e>0){const s=Date.now();for(;Date.now()-s<5e3;)try{process.kill(e,0),await new Promise(n=>setTimeout(n,100))}catch{break}try{process.kill(e,"SIGKILL")}catch{}}this.releaseNotifyPortReservation(),this.internalApi&&(await this.internalApi.stop(),this.internalApi=null)}isAlive(){return this.alive}async createSession(e){return`claude-session-${Date.now()}`}async resumeSession(e,t){}async destroySession(e){}sendPrompt(e){const t=new bi(e.adapterSessionId),i={event_id:e.adapterSessionId,session_id:e.adapterSessionId,content:e.text,context_messages_json:e.contextMessages?JSON.stringify(e.contextMessages):void 0};return this.deliverInboundEvent(i),this.once(`reply:${e.adapterSessionId}`,s=>{t.emitDone(s.status==="completed"?{status:"completed"}:{status:"failed",error:"failed"})}),t}async cancel(e){}setPermissionHandler(e){}async ping(e){if(this.stopped)return!1;if(!this.claudeProcess&&!this.claudePty)return this.alive&&this.internalApi!==null;if(!(this.alive&&this.mcpServerReady&&(this.startupChannelListening||process.platform==="win32")))return!1;const t=this.claudeProcess?.pid??this.claudePty?.pid;if(t&&!di(t))return!1;const i=this.claudeProcess;if(i){const s=!!(i.stdin&&!i.stdin.destroyed),n=!!(i.stdout&&!i.stdout.destroyed);if(!s||!n)return!1}return!0}getStatus(){return{alive:this.alive,busy:this.activeEvent!==null||this.compacting,backgroundBusy:this.selfDrivenReported,sessions:0,details:this.sessionState?{sessionState:this.sessionState}:void 0}}getActiveEventIds(){return this.activeEvent?[this.activeEvent.eventId]:[]}takeAuditBoundary(e){const t=this.completedAuditBoundaries.get(e);return t&&this.completedAuditBoundaries.delete(e),t}clearActiveEventForShutdown(){this.activeEvent&&this.clearActiveEvent()}getSessionState(){return this.sessionState}getMcpConfig(){return null}async hasBackgroundWork(){const e=this.claudeProcess?.pid??this.claudePty?.pid;if(!e)return!1;const t=[this.claudeProcess?.pid,this.claudeChildPid||void 0,this.claudePty?.pid].filter(i=>!!i&&i>0);return ut(e,t)}async probe(e){const t=this.config.command||"claude",i=await Ut(t),s=i!==null;let n=null,r;if(s){const h=await Ht(t);n=h.version,h.error&&(r=h.error)}else r={code:"cli_not_found",message:`command not found: ${t}`};const l=(Bt().ANTHROPIC_BASE_URL??process.env.ANTHROPIC_BASE_URL??"").trim()||null,c=(this.sessionState?.model?.id??(process.env.ANTHROPIC_MODEL??"").trim())||null;let u={attempted:!1,ok:!1,latency_ms:null};if(e?.conversation&&s){const h=Date.now();try{const p=dt(ct),m=["-p","ping","--output-format","json"];this.getClaudeCliCapabilities(t).maxTurns&&m.push("--max-turns","1");const v=ye(t,m);await p(v.command,v.args,{timeout:e.timeoutMs??8e3,encoding:"utf-8",windowsVerbatimArguments:v.windowsVerbatimArguments,env:V()}),u={attempted:!0,ok:!0,latency_ms:Date.now()-h}}catch(p){const m=p,v=m.killed?"conversation_timeout":"conversation_failed";u={attempted:!0,ok:!1,latency_ms:Date.now()-h,error:{code:v,message:m.message??String(p)}}}}return{cli:{command:t,installed:s,path:i,version:n,...r?{error:r}:{}},conversation:u,config:{model:c,base_url:l,source:{model:this.sessionState?.model?.id?"runtime":process.env.ANTHROPIC_MODEL?"env":"unknown",base_url:l?"env":"unknown"}},process:{started:!!(this.claudeProcess||this.claudePty),alive:this.alive,busy:this.activeEvent!==null},...this.probeSessionRecord()}}probeSessionRecord(){const e=this.claudeCliSessionId;if(!e||!this.claudeSessionCwd)return{session:{recordPath:null,lastActivityMs:null,freshMs:null}};const t=O(e,this.claudeSessionCwd);try{const i=I(t);return{session:{recordPath:t,lastActivityMs:i.mtimeMs,freshMs:Date.now()-i.mtimeMs}}}catch{return{session:{recordPath:t,lastActivityMs:null,freshMs:null}}}}getSupportedCommands(){return[{name:"compact",description:"\u538B\u7F29\u4E0A\u4E0B\u6587",args:"[instructions]"},{name:"clear",description:"\u6E05\u9664\u5BF9\u8BDD"},{name:"model",description:"\u5207\u6362\u6A21\u578B",args:"<model-id>"},{name:"cost",description:"\u663E\u793A\u8D39\u7528"},{name:"rewind",description:"\u56DE\u9000\u5BF9\u8BDD"},{name:"memory",description:"\u8BB0\u5FC6\u7BA1\u7406"},{name:"doctor",description:"\u8BCA\u65AD"},{name:"status",description:"\u72B6\u6001\u663E\u793A"},{name:"skills",description:"\u83B7\u53D6 skills \u6E05\u5355"}]}async execCommand(e,t,i){if(e==="skills")try{const n=Lt({mode:"claude",projectDir:this.claudeSessionCwd}),r=n.map(a=>{const l=a.trigger?` (${a.trigger})`:"",c=a.pluginName?` [plugin:${a.pluginName}]`:` [${a.source}]`;return`- ${a.name}${l}${c}: ${a.description}`});return{status:"ok",message:r.length>0?r.join(`
1
+ import{spawn as se,spawnSync as ne,execSync as re,execFile as ct}from"node:child_process";import{promisify as dt}from"node:util";import{randomUUID as J}from"node:crypto";import{killProcessGroup as q,hasChildProcesses as ut,resolveCommandPath as ht,resolveWindowsExecInvocation as ye}from"../../core/runtime/spawn.js";import{chmodSync as ae,readdirSync as oe,readFileSync as B,realpathSync as we,rmSync as ft,statSync as I,truncateSync as pt,existsSync as S,openSync as mt,writeSync as vt,closeSync as gt,renameSync as Pe,mkdirSync as Ce,constants as Te,watch as Et}from"node:fs";import{appendFile as _t,mkdir as L,readFile as T,rm as Ae,stat as Ie,writeFile as D}from"node:fs/promises";import{dirname as St,isAbsolute as yt,join as f,resolve as wt}from"node:path";import{homedir as k,tmpdir as Pt}from"node:os";import le from"node:net";import{EventEmitter as be}from"node:events";import{fileURLToPath as Ct}from"node:url";let Y=null;if(process.platform==="win32")try{Y=await import("node-pty")}catch{Y=null}import{resolveRuntimePaths as y}from"../../core/config/index.js";import{normalizeClaudeEffort as Tt,normalizeClaudeEnvEffort as At}from"../../core/config/claude-direct-env.js";import{SESSION_MODE_IDS as G}from"./protocol-contract.js";import{buildSessionContextBlock as It}from"../shared/session-context.js";import{log as o}from"../../core/log/index.js";import{ActivityStatusManager as bt}from"./activity-status-manager.js";import{HookSignalStore as kt}from"../../core/hooks/hook-signal-store.js";import{QuestionStore as ke}from"../../core/persistence/question-store.js";import{PermissionStore as z}from"../../core/persistence/permission-store.js";import{InternalApiServer as Rt}from"../../core/mcp/internal-api-server.js";import{executeEventTool as Re,isEventTool as Dt}from"../../core/mcp/event-tool-executor.js";import{validateToolArgs as $t}from"../../core/mcp/tool-schemas.js";import{isGrixInternalToolName as Ot,normalizeEventToolArgs as xt}from"../../core/mcp/tools.js";import{scanSkills as Lt}from"./skill-scanner.js";import{syncDefaultSkillsToDir as Mt}from"../../default-skills/index.js";import{clearSessionJsonlDriftCache as Ft,countPendingBackgroundTasks as Nt,noteSessionJsonlPath as jt,extractLastAssistantEntry as W,extractTextBlocksAfterOffset as De,hasTerminalToolResultAfterOffset as $e,probeSessionTurnState as ce,resolveSessionJsonlPath as O}from"./usage-parser.js";import{readSettingsEnv as Bt}from"./model-list.js";import{resolveCliPath as Ut,getCliVersion as Ht}from"../../core/util/cli-probe.js";import{GRIX_RELAY_ANTHROPIC_API_KEY as Oe}from"../../core/proxy/index.js";const M="grix",K={developmentChannels:!0,maxTurns:!0};function qt(d){if(!Array.isArray(d))return;const e=d.map(t=>{if(typeof t=="string")return t.trim();if(!t||typeof t!="object")return"";const i=t,s=String(i.label??"").trim();if(s)return s;const n=String(i.value??"").trim();if(n)return n;const r=String(i.text??"").trim();return r||""}).filter(t=>t.length>0);return e.length>0?e:void 0}function xe(d){let e;try{e=JSON.parse(d)}catch{return null}const t=e.questions;if(!Array.isArray(t)||t.length===0)return null;const i=[];for(let s=0;s<t.length;s++){const n=t[s];if(!n||typeof n!="object")continue;const r=n,a=`Question ${s+1}`,l=String(r.header??r.question??a).trim()||a,c=String(r.prompt??r.question??"").trim();if(!c)continue;const u=qt(r.options),h=r.multiSelect===!0||r.multi_select===!0;i.push({header:l,prompt:c,...u?{options:u}:{},...h?{multi_select:!0}:{}})}return i.length>0?i:null}function Gt(d){if(!d||typeof d!="string")return null;try{const e=JSON.parse(d);return(typeof e.plan=="string"?e.plan.trim():"")||null}catch{return null}}const zt=[/Please run \/login/,/API Error:\s*401/,/authentication_error/,/OAuth token has expired/],Wt=[/API Error:\s*401/,/authentication_error/],Jt=[/You're out of extra usage/i,/Stop and wait for limit to reset/i,/Add funds to continue with extra usage/i],Yt=/API Error:\s*400.*(server_tool_use\.id|messages\.\d+\.content)/;function de(d){const e=d?.trim();return e&&!e.startsWith("API Error:")?e:"Claude hit an API format error. Please start a new conversation (/grix open <dir>)."}const Le=300*1e3,Me=300*1e3,Fe=Me+30*1e3,Ne=3e3,Kt=50,Vt=1800*1e3,Qt=["CLAUDE_CODE_ENABLE_TELEMETRY","CLAUDE_CODE_ENHANCED_TELEMETRY_BETA","OTEL_LOGS_EXPORTER","OTEL_TRACES_EXPORTER","OTEL_METRICS_EXPORTER","OTEL_EXPORTER_OTLP_PROTOCOL","OTEL_EXPORTER_OTLP_ENDPOINT","OTEL_EXPORTER_OTLP_HEADERS","OTEL_LOGS_EXPORT_INTERVAL","OTEL_TRACES_EXPORT_INTERVAL","OTEL_LOG_USER_PROMPTS","OTEL_LOG_ASSISTANT_RESPONSES","OTEL_LOG_TOOL_DETAILS","OTEL_LOG_TOOL_CONTENT","OTEL_LOG_RAW_API_BODIES"];function V(d=process.env){const e={...d},t=new Set(Qt.map(i=>i.toLowerCase()));for(const i of Object.keys(e))t.has(i.toLowerCase())&&delete e[i];return e}const je=60*1e3,Be="lost injection respawn",Ue=600*1e3,Xt=250;function He(d,e){const t=Number(process.env[d]);return Number.isFinite(t)&&t>0?t:e}function qe(d){return d.trim().replace(/[^a-zA-Z0-9._-]+/gu,"_")||"unknown"}function Zt(d){const e=ht("expect",d);return yt(e)?e:null}function Q(d){try{return S(d)?I(d).size:0}catch{return 0}}function Ge(d){const e=[],t=i=>{let s;try{s=oe(i,{withFileTypes:!0})}catch{return}for(const n of s){const r=f(i,n.name);n.isDirectory()?t(r):n.isFile()&&e.push(r)}};return t(d),e.sort((i,s)=>ze(i)-ze(s)||i.localeCompare(s))}function ze(d){try{return I(d).mtimeMs}catch{return 0}}const We=He("GRIX_RESCUE_SETTLE_POLL_MS",2e3),Je=He("GRIX_RESCUE_SETTLE_BUDGET_MS",12e4),ei=300*1e3,ti="The model's tool call could not be parsed (retry also failed).",ii="[\u7CFB\u7EDF\u81EA\u52A8\u91CD\u8BD5] \u4F60\u4E0A\u4E00\u6761\u56DE\u590D\u7684\u5DE5\u5177\u8C03\u7528\u89E3\u6790\u5931\u8D25\uFF0C\u6CA1\u6709\u53D1\u51FA\u53BB\u3002\u8BF7\u4E0D\u8981\u91CD\u590D\u6267\u884C\u5DF2\u7ECF\u5B8C\u6210\u7684\u64CD\u4F5C\uFF0C\u76F4\u63A5\u91CD\u65B0\u8C03\u7528 reply \u5DE5\u5177\uFF0C\u628A\u4F60\u4E0A\u4E00\u6761\u60F3\u53D1\u7ED9\u7528\u6237\u7684\u7ED3\u8BBA\u539F\u6837\u91CD\u65B0\u53D1\u9001\u4E00\u6B21\u3002",si="Sorry, the previous reply could not be generated properly (the model's tool call failed to parse). Please send it again, and use /grix restart to restart the session if needed.",ni=2*1e3,ue=90*1e3,Ye=1800*1e3,Ke=15*1e3,ri=60*1e3,Ve=10*1e3,ai=30*1e3,Qe=30*1e3,oi=2e3,li=3e4;function ci(d){const e=d.tool_name||"";if(e==="mcp__grix__grix_dispatch_agent")return"\u540E\u53F0\u534F\u8C03\u5B50 agent";if(e.startsWith("mcp__grix__"))return"\u540E\u53F0\u8C03\u7528 Grix \u5DE5\u5177";if(e.startsWith("mcp__"))return"\u540E\u53F0\u8C03\u7528\u5DE5\u5177";switch(e){case"Bash":return"\u540E\u53F0\u6267\u884C\u547D\u4EE4";case"Read":case"Edit":case"Write":case"NotebookEdit":return"\u540E\u53F0\u8BFB\u5199\u6587\u4EF6";case"Task":return"\u540E\u53F0\u8FD0\u884C\u5B50\u4EFB\u52A1";default:return e?`\u540E\u53F0\u8FD0\u884C\uFF1A${e}`:"\u540E\u53F0\u4EFB\u52A1\u8FDB\u884C\u4E2D"}}function di(d){try{return process.kill(d,0),!0}catch(e){return e.code==="EPERM"}}const ui=12e4,hi=3e4,fi=6e4,pi=3e3,X=1e3,Xe=45e3,mi=18e3,he=8192,vi=1e3,gi=2e3,Ze=300,Ei=3e4,_i=2e3,Si=2e3,yi=/(do you want to proceed\?[\s\S]*?\d\.\s*yes)|(only you can approve)/i,wi=/(do you want to proceed)|(only you can approve)|(\d\.\s*yes)|(\d\.\s*no)|(esc to cancel)|(tab to amend)/i,Pi=/^[✳✶✻✽✵❋✺✴❈❖✦✧✢◉◎⬥⬦◇◆▸▹►▻→←↑↓⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏·…\d]*still\s+thinking/i,fe="__grix_key__ ";let et=!1;const pe=new Set;async function Ci(d=[]){const e=new Set(d.filter(t=>Number.isInteger(t)&&t>0));for(let t=0;t<20;t++){const i=await new Promise((s,n)=>{const r=le.createServer();r.once("error",n),r.listen(0,"127.0.0.1",()=>{const a=r.address(),l=typeof a=="object"&&a?a.port:0;r.close(c=>{if(c){n(c);return}s(l)})})});if(i>0&&!e.has(i)&&!pe.has(i))return pe.add(i),i}throw new Error("\u65E0\u6CD5\u5206\u914D MCP \u901A\u77E5\u7AEF\u53E3")}function Ti(d){d>0&&pe.delete(d)}const Ai=["You are connected to a chat via the grix-claude MCP server.",'Messages arrive as <channel source="grix-claude" chat_id="..." event_id="..." message_id="..." user_id="...">text</channel>.',"When present, the channel metadata includes context_messages_json \u2014 a JSON array of earlier visible messages for this trigger; each entry carries sender_id and content. An entry whose content starts with [\u5F15\u7528\u6D88\u606F] is the quoted (replied-to) message being responded to. Use these records as context (prefer them over inferring from quoted_message_id alone), in group chats use sender_id to tell who said what, and only answer the current message.","Local transcript availability, session binding, recovery mode, and connector history-loss diagnostics are internal operational details. Do not mention them, ask the user to choose a recovery mode, or say that history/context was lost unless the user explicitly asks to debug that mechanism.",'Both your plain text and the reply tool are delivered to the user. You are talking directly to the user, so address them in the second person ("you"); do not slip into third-person narration about them (e.g. "\u5DF2\u7ECF\u628A\u7ED3\u8BBA\u56DE\u590D\u7ED9\u8001\u90ED\u4E86") \u2014 say it straight to them ("\u7ED3\u8BBA\u662F\u2026\u2026").',"While you work, use plain text for short progress notes. Deliver your final conclusion \u2014 the answer the user is waiting for \u2014 through the reply tool; that is the message the user treats as your result and the one the connector quotes. Your plain text reaches the user too \u2014 it is not private scratch space, so it is a real channel for progress and for anything the user needs mid-turn; you just need not restate the full conclusion as plain text once it is in the reply. The connector automatically quotes the message you are answering, so you normally do not need to set reply_to (set it only to quote a different earlier message).","Make the reply tool call the last thing you do in a turn and put your whole conclusion inside it: once you have called reply, any further text you write in the same turn is dropped, not delivered. Progress notes go before the reply; the reply ends the turn.",'Never explain the delivery mechanics to the user or tell them you "already sent it via the reply tool" \u2014 that is internal plumbing they neither see nor care about. If the user says they did not receive or cannot read something, do not insist you already sent it; assume the delivery or the format failed and send it again, switching format when that is the likely cause (e.g. render a diagram to an image, strip code fences, upload a file).',"If you intentionally do not want to send any visible message, call the complete tool with event_id and a final status.",'Do not send a bare acknowledgement or "got it / on it / starting now" message before doing the work; do the work first, then deliver the actual result through the reply tool. During a long-running task, write a short plain-text progress note when you reach a meaningful milestone so the user can see progress as it happens.',"You only run while handling a turn; once your turn ends you are frozen and cannot send anything until the user messages you again.",'Unbreakable rule: never end a turn having promised something you cannot deliver. In particular, never start a task, detach it (nohup, a trailing &, any background process or watcher), end the turn, and promise to "notify when it finishes" \u2014 that notification can never happen, so do not promise it.',"For any task whose result the user is waiting on (deploy, build, rollout, tests), run it in the foreground and wait for it to finish within this same turn, then report the real outcome. You may launch several in the background at once for parallelism, but you must still collect all of their results within this same turn before you reply and finish.","Two cases where leaving a process running is correct: a long-lived service that is meant to keep running (dev server, daemon) \u2014 background it, then report that it started plus how to check or stop it; and a task too long to finish in one turn \u2014 you may leave it running, but state plainly that you cannot auto-notify and give the user a concrete way to retrieve the result later (a log path, a PID, or asking them to ping you to re-check)."];function Ii(d,e,t){const i=[...Ai];e&&i.unshift(It(e,t));try{const s=d?.agentName?.trim().slice(0,200),n=d?.introduction?.trim().slice(0,2e3);if(s||n){const r=[s?`Your name is "${s}".`:"",n?`Your self-introduction: ${n}`:""].filter(Boolean).join(" ");i.unshift(r)}}catch{}return i.join(" ")}function tt(d){return String(d??"").trim().toLowerCase()===G.approval?G.approval:G.fullAuto}class Ss extends be{type="claude";config;bridgeCallbacks;mcpServerProcess=null;internalApi=null;claudeProcess=null;claudePty=null;singleTurnFallbackProcesses=new WeakSet;singleTurnFallbackPtys=new WeakSet;lostInjectionKilledProcesses=new WeakSet;lostInjectionKilledPtys=new WeakSet;singleTurnFallbackChild=null;spawnPromise=null;lifecycleVersion=0;sessionId="";alive=!1;stopped=!1;activeEvent=null;completedAuditBoundaries=new Map;activeEventIdleTimer=null;activeEventHardTimer=null;activeEventPostReplyTimer=null;activeEventPostReplyWatcher=null;activeEventPostReplyPoll=null;rescueSettleTimer=null;stopHookBarrierSessionId=null;stopHookBarrierTimer=null;selfDrivenActive=!1;selfDrivenLastSignalAt=0;selfDrivenSweepTimer=null;selfDrivenReported=!1;selfDrivenReportTimer=null;selfDrivenLabel=null;selfDrivenTextOffset=null;selfDrivenDeliverTimer=null;selfDrivenReplied=!1;compacting=!1;compactingTimer=null;compactionDoneResolver=null;mcpServerReady=!1;mcpStartupFailureHandled=!1;mcpChannelBroken=!1;channelGateClosed=!1;startupChannelListening=!1;startupChannelListeningAt=0;startupDialogSeenAt=0;windowsTrustDialogAnswered=!1;windowsTrustDialogTimer=null;devChannelsConfirmTimer=null;ptyDialogBusy=!1;pendingMcpFailureTimer=null;sessionIdConflictDetected=!1;sessionIdConflictRetriedEventIds=new Set;malformedToolRetriedEventIds=new Set;lostInjectionRetriedEventIds=new Set;runtimeResolver=null;activityManager=null;deferredModelId=null;claudeCliCapabilities=null;claudeChildPid=0;claudeCliSessionId="";claudeSessionCwd="";claudeMcpConfigPath="";expectRunDir="";auditEvidencePaths=null;sessionState=null;authFailureUntil=0;usageLimitUntil=0;completedEventIds=new Map;pendingQuestion=new Map;pendingPermissions=new Map;lastClearedEvent=null;textForwardSeq=0;constructor(e,t){super(),this.config=e,this.bridgeCallbacks=t;const s=(e.options??{}).sessionRuntimeResolver;typeof s=="function"&&(this.runtimeResolver=s)}async start(){this.lifecycleVersion+=1,this.stopped=!1,this.alive=!0,this.internalApi=new Rt,this.internalApi.setInvokeHandler(async(e,t,i,s)=>this.handleInternalInvoke(e,t,i,s)),this.internalApi.setStatusLineHandler(async e=>{this.handleStatusLineUpdate(e)}),await this.internalApi.start(0),this.internalApi.setMcpBridgeUpHandler(e=>this.onMcpBridgeUp(e)),this.internalApiPort=parseInt(new URL(this.internalApi.baseUrl).port,10),this.notifyPort=0,o.info("claude-adapter",`Adapter started (stdio MCP mode, internal API at ${this.internalApi.baseUrl})`)}onMcpBridgeUp(e){this.bridgeCallbacks.sendMcpFrame?.(e)}deliverMcpFrameToAgent(e){this.internalApi?.sendMcpFrameToBridge(e)}async stop(){if(o.info("claude-adapter",`Stopping adapter (sessionId=${this.sessionId}, alive=${this.alive}, activeEvent=${this.activeEvent?.eventId??"none"})`),this.lifecycleVersion+=1,this.stopped=!0,this.alive=!1,this.activeEvent&&(this.bridgeCallbacks.sendEventResult(this.activeEvent.eventId,"canceled","adapter stopped"),this.clearActiveEvent()),this.stopComposing(),this.claudeCliSessionId&&Ft(this.claudeCliSessionId),this.stopSelfDriven(),this.clearSelfDrivenDeliverTimer(),this.clearStopHookBarrier(),this.compacting=!1,this.compactingTimer&&(clearTimeout(this.compactingTimer),this.compactingTimer=null),this.compactionDoneResolver&&(this.compactionDoneResolver("stopped"),this.compactionDoneResolver=null),this.mcpChannelBroken=!1,this.channelGateClosed=!1,this.startupChannelListening=!1,this.startupChannelListeningAt=0,this.startupDialogSeenAt=0,this.deferredModelId=null,this.clearPendingMcpFailureTimer(),this.clearWindowsTrustDialogTimer(),this.windowsTrustDialogAnswered=!1,this.clearDevChannelsConfirmTimer(),this.ptyDialogBusy=!1,this.terminalPermissionKeyTimer&&(clearTimeout(this.terminalPermissionKeyTimer),this.terminalPermissionKeyTimer=null),this.terminalPermissionPromptTail="",this.terminalPermissionPromptVisible=!1,this.pendingPermissions.size>0){const s=y(),n=new z(s.permissionRequestsDir);for(const[r]of this.pendingPermissions)n.resolveRequest(r,"deny").catch(()=>{});this.pendingPermissions.clear()}if(this.activityManager&&(this.activityManager.stop(),this.activityManager=null),this.sessionId){const s=this.resolveHookSignalsPath();Ae(s).catch(()=>{})}this.stopMcpServer();const e=this.claudeChildPid;this.claudeChildPid=0;const t=this.claudeProcess,i=this.claudePty;if(this.claudeProcess=null,this.claudePty=null,this.spawnPromise=null,i)try{i.kill()}catch{}if(e>0)try{process.kill(e,"SIGTERM")}catch{}if(t?.pid&&(q(t,"SIGTERM"),!await Promise.race([new Promise(n=>{t.once("exit",()=>n(!0))}),new Promise(n=>{setTimeout(()=>n(!1),5e3)})]))){if(e>0)try{process.kill(e,"SIGKILL")}catch{}q(t,"SIGKILL")}if(e>0){const s=Date.now();for(;Date.now()-s<5e3;)try{process.kill(e,0),await new Promise(n=>setTimeout(n,100))}catch{break}try{process.kill(e,"SIGKILL")}catch{}}this.releaseNotifyPortReservation(),this.internalApi&&(await this.internalApi.stop(),this.internalApi=null)}isAlive(){return this.alive}async createSession(e){return`claude-session-${Date.now()}`}async resumeSession(e,t){}async destroySession(e){}sendPrompt(e){const t=new bi(e.adapterSessionId),i={event_id:e.adapterSessionId,session_id:e.adapterSessionId,content:e.text,context_messages_json:e.contextMessages?JSON.stringify(e.contextMessages):void 0};return this.deliverInboundEvent(i),this.once(`reply:${e.adapterSessionId}`,s=>{t.emitDone(s.status==="completed"?{status:"completed"}:{status:"failed",error:"failed"})}),t}async cancel(e){}setPermissionHandler(e){}async ping(e){if(this.stopped)return!1;if(!this.claudeProcess&&!this.claudePty)return this.alive&&this.internalApi!==null;if(!(this.alive&&this.mcpServerReady&&(this.startupChannelListening||process.platform==="win32")))return!1;const t=this.claudeProcess?.pid??this.claudePty?.pid;if(t&&!di(t))return!1;const i=this.claudeProcess;if(i){const s=!!(i.stdin&&!i.stdin.destroyed),n=!!(i.stdout&&!i.stdout.destroyed);if(!s||!n)return!1}return!0}getStatus(){return{alive:this.alive,busy:this.activeEvent!==null||this.compacting,backgroundBusy:this.selfDrivenReported,sessions:0,details:this.sessionState?{sessionState:this.sessionState}:void 0}}getActiveEventIds(){return this.activeEvent?[this.activeEvent.eventId]:[]}takeAuditBoundary(e){const t=this.completedAuditBoundaries.get(e);return t&&this.completedAuditBoundaries.delete(e),t}clearActiveEventForShutdown(){this.activeEvent&&this.clearActiveEvent()}getSessionState(){return this.sessionState}getMcpConfig(){return null}async hasBackgroundWork(){const e=this.claudeProcess?.pid??this.claudePty?.pid;if(!e)return!1;const t=[this.claudeProcess?.pid,this.claudeChildPid||void 0,this.claudePty?.pid].filter(i=>!!i&&i>0);return ut(e,t)}async probe(e){const t=this.config.command||"claude",i=await Ut(t),s=i!==null;let n=null,r;if(s){const h=await Ht(t);n=h.version,h.error&&(r=h.error)}else r={code:"cli_not_found",message:`command not found: ${t}`};const l=(Bt().ANTHROPIC_BASE_URL??process.env.ANTHROPIC_BASE_URL??"").trim()||null,c=(this.sessionState?.model?.id??(process.env.ANTHROPIC_MODEL??"").trim())||null;let u={attempted:!1,ok:!1,latency_ms:null};if(e?.conversation&&s){const h=Date.now();try{const p=dt(ct),m=["-p","ping","--output-format","json"];this.getClaudeCliCapabilities(t).maxTurns&&m.push("--max-turns","1");const v=ye(t,m);await p(v.command,v.args,{timeout:e.timeoutMs??8e3,encoding:"utf-8",windowsVerbatimArguments:v.windowsVerbatimArguments,env:V()}),u={attempted:!0,ok:!0,latency_ms:Date.now()-h}}catch(p){const m=p,v=m.killed?"conversation_timeout":"conversation_failed";u={attempted:!0,ok:!1,latency_ms:Date.now()-h,error:{code:v,message:m.message??String(p)}}}}return{cli:{command:t,installed:s,path:i,version:n,...r?{error:r}:{}},conversation:u,config:{model:c,base_url:l,source:{model:this.sessionState?.model?.id?"runtime":process.env.ANTHROPIC_MODEL?"env":"unknown",base_url:l?"env":"unknown"}},process:{started:!!(this.claudeProcess||this.claudePty),alive:this.alive,busy:this.activeEvent!==null},...this.probeSessionRecord()}}probeSessionRecord(){const e=this.claudeCliSessionId;if(!e||!this.claudeSessionCwd)return{session:{recordPath:null,lastActivityMs:null,freshMs:null}};const t=O(e,this.claudeSessionCwd);try{const i=I(t);return{session:{recordPath:t,lastActivityMs:i.mtimeMs,freshMs:Date.now()-i.mtimeMs}}}catch{return{session:{recordPath:t,lastActivityMs:null,freshMs:null}}}}getSupportedCommands(){return[{name:"compact",description:"\u538B\u7F29\u4E0A\u4E0B\u6587",args:"[instructions]"},{name:"clear",description:"\u6E05\u9664\u5BF9\u8BDD"},{name:"model",description:"\u5207\u6362\u6A21\u578B",args:"<model-id>"},{name:"cost",description:"\u663E\u793A\u8D39\u7528"},{name:"rewind",description:"\u56DE\u9000\u5BF9\u8BDD"},{name:"memory",description:"\u8BB0\u5FC6\u7BA1\u7406"},{name:"doctor",description:"\u8BCA\u65AD"},{name:"status",description:"\u72B6\u6001\u663E\u793A"},{name:"skills",description:"\u83B7\u53D6 skills \u6E05\u5355"}]}async execCommand(e,t,i){if(e==="skills")try{const n=Lt({mode:"claude",projectDir:this.claudeSessionCwd}),r=n.map(a=>{const l=a.trigger?` (${a.trigger})`:"",c=a.pluginName?` [plugin:${a.pluginName}]`:` [${a.source}]`;return`- ${a.name}${l}${c}: ${a.description}`});return{status:"ok",message:r.length>0?r.join(`
2
2
  `):"No skills found",data:n}}catch(n){return{status:"failed",message:`Failed to scan skills: ${n instanceof Error?n.message:n}`}}if(!this.claudeProcess&&!this.claudePty)if(e==="compact"){try{await this.ensureClaudeProcessReady()}catch(n){return{status:"failed",message:`Failed to start Claude for compact: ${n instanceof Error?n.message:n}`}}if(await new Promise(n=>setTimeout(n,2e3)),!this.claudeProcess&&!this.claudePty)return{status:"failed",message:"Claude process exited during startup"};try{await this.waitForChannelReady()}catch(n){return{status:"failed",message:`Claude channel not ready for compact: ${n instanceof Error?n.message:n}`}}}else return{status:"failed",message:"Claude process is not running"};if(this.getStatus().busy)return{status:"failed",message:"Claude is busy, try again later"};const s=t?`/${e} ${t}`:`/${e}`;try{if(this.claudePty?this.claudePty.write(`${s}\r`):this.expectRunDir?this.writeExpectFifoLine(this.expectRunDir,s):this.claudeProcess?.stdin?.write(`${s}
3
3
  `),e==="compact"){const n=new Promise(a=>{this.compactionDoneResolver=a});this.beginCompaction();const r=await n;return r==="process-exit"?{status:"failed",message:"Claude exited during compaction"}:r==="stopped"?{status:"failed",message:"Adapter stopped during compaction"}:{status:"ok",message:"Compacted"}}return{status:"ok",message:`Sent: ${s}`}}catch(n){return{status:"failed",message:`Failed to send: ${n instanceof Error?n.message:n}`}}}writeExpectFifoLine(e,t){const i=f(e,"cmd.fifo");let s;try{s=mt(i,Te.O_WRONLY|Te.O_NONBLOCK)}catch(n){const r=n.code;throw new Error(r==="ENXIO"?"expect process is not running \u2014 cannot inject command":`FIFO open failed: ${n instanceof Error?n.message:n}`)}try{vt(s,`${t}
4
- `)}finally{gt(s)}}handleStatusLineUpdate(e){try{const t=e.context_window,i=e.cost,s=e.model,n=e.rate_limits??e.rateLimits,r=t?.current_usage;if(this.sessionState={contextWindow:{usedPercentage:t?.used_percentage!=null?w(t.used_percentage):null,remainingPercentage:t?.remaining_percentage!=null?w(t.remaining_percentage):null,totalInputTokens:w(t?.total_input_tokens),totalOutputTokens:w(t?.total_output_tokens),contextWindowSize:w(t?.context_window_size)||2e5,currentUsage:r?{inputTokens:w(r.input_tokens),outputTokens:w(r.output_tokens),cacheCreationInputTokens:w(r.cache_creation_input_tokens),cacheReadInputTokens:w(r.cache_read_input_tokens)}:null},cost:{totalCostUsd:w(i?.total_cost_usd),totalDurationMs:w(i?.total_duration_ms),totalApiDurationMs:w(i?.total_api_duration_ms),totalLinesAdded:w(i?.total_lines_added),totalLinesRemoved:w(i?.total_lines_removed)},rateLimits:this.parseStatusRateLimits(n),model:{id:String(s?.id??""),displayName:String(s?.display_name??"")},fastMode:e.fast_mode===!0,effort:e.effort!=null?String(e.effort):void 0,thinkingEnabled:e.thinking_enabled===!0,exceeds200kTokens:e.exceeds_200k_tokens===!0,version:String(e.version??""),sampledAt:Date.now()},n){const a=this.sessionState.rateLimits?.fiveHour,l=this.sessionState.rateLimits?.sevenDay;o.info("claude-adapter",`[rate-limits] statusLine parsed: fiveHour=${a?`${a.usedPercentage}% resetsAt=${a.resetsAt}`:"n/a"} sevenDay=${l?`${l.usedPercentage}% resetsAt=${l.resetsAt}`:"n/a"}`)}else o.debug("claude-adapter","[rate-limits] statusLine: no rate_limits in payload");try{this.bridgeCallbacks.onStatusLineUpdated?.(this.sessionState)}catch(a){o.warn("claude-adapter",`onStatusLineUpdated callback error: ${a instanceof Error?a.message:a}`)}}catch(t){o.warn("claude-adapter",`Failed to parse statusLine payload: ${t instanceof Error?t.message:t}`)}}parseStatusRateLimits(e){if(!e)return;const t=n=>{if(!n||typeof n!="object")return;const r=n,a=w(r.used_percentage??r.usedPercent),l=w(r.resets_at??r.resetsAt);if(!(a<=0&&l<=0))return{usedPercentage:a,resetsAt:l}},i=t(e.five_hour??e.fiveHour),s=t(e.seven_day??e.sevenDay);if(!(!i&&!s))return{...i?{fiveHour:i}:{},...s?{sevenDay:s}:{}}}deliverInboundEvent(e){if(this.sessionId||(this.sessionId=e.session_id),this.pruneCompletedEvents(),this.completedEventIds.has(e.event_id)){o.info("claude-adapter",`Event ${e.event_id} rejected: duplicate`),this.bridgeCallbacks.sendEventResult(e.event_id,"responded","duplicate event");return}if(Date.now()<this.authFailureUntil){o.info("claude-adapter",`Event ${e.event_id} rejected: auth cooldown`),this.bridgeCallbacks.sendEventResult(e.event_id,"failed","Claude authentication failed \u2014 please re-login");return}if(Date.now()<this.usageLimitUntil){o.info("claude-adapter",`Event ${e.event_id} rejected: usage limit cooldown`),this.bridgeCallbacks.sendEventResult(e.event_id,"failed","Claude usage limit reached \u2014 waiting for reset");return}if(this.activeEvent?.eventId===e.event_id){o.info("claude-adapter",`Event ${e.event_id} ignored: same as active event`);return}if(this.activeEvent||this.compacting||this.stopHookBarrierSessionId===e.session_id){o.error("claude-adapter",`Scheduler invariant violated: event ${e.event_id} delivered while busy (active=${this.activeEvent?.eventId??"none"}, compacting=${this.compacting}, barrier=${this.stopHookBarrierSessionId===e.session_id}) \u2014 failing event to release slot`),this.bridgeCallbacks.sendEventResult(e.event_id,"failed","agent busy (scheduler invariant violated)");return}this.activeEvent={eventId:e.event_id,sessionId:e.session_id,rawEvent:e},this.stopSelfDriven(),this.clearSelfDrivenDeliverTimer(),this.lastClearedEvent=null,this.markActiveEventActivity(e.event_id,e.session_id),this.resetActiveEventHardTimer(e.event_id),this.emit("eventStarted",e.event_id,e.session_id),this.ensureClaudeAndPushEvent(e)}deliverStopEvent(e,t){if(this.activeEvent?.eventId!==e){o.info("claude-adapter",`Stop for non-active event=${e} (active=${this.activeEvent?.eventId??"none"}, compacting=${this.compacting}) \u2014 emitting eventDone to unblock platform`),this.emit("eventDone",e);return}const i=this.activeEvent.sessionId;o.info("claude-adapter",`Stop requested for active event=${e} \u2014 killing Claude process`),this.mcpServerReady&&this.pushNotification("notifications/event_stop",{event_id:e,session_id:i,stop_id:J()}),(this.claudeProcess||this.claudePty)&&this.emit("pauseIntake","restart"),this.bridgeCallbacks.sendEventResult(e,"canceled","stopped by user"),this.clearActiveEvent(),this.killClaudeProcess("stop")}async handleLocalAction(e){if(!this.mcpServerReady)return{handled:!1,kind:""};const t=String(e.action_type??"");if(t==="claude_interaction_reply"){const i=e.params??{};if(String(i.kind??"")==="permission"){const n=i.resolution??{},r=String(n.value??""),a={...e,action_type:r==="allow"?"exec_approve":"exec_reject",params:{...i,approval_command_id:i.request_id}};return this.handlePermissionApproval(a)}return this.handleQuestionReply(e)}return t==="exec_approve"||t==="exec_reject"?this.handlePermissionApproval(e):(this.pushNotification("notifications/local_action",e),{handled:!0,kind:""})}async handleQuestionReply(e){const t=e.params??{},i=String(t.request_id??"");if(!i)return o.warn("claude-adapter","Question reply missing request_id"),this.bridgeCallbacks.sendLocalActionResult(e.action_id,"failed",void 0,"request_id_required","request_id is required"),{handled:!0,kind:"question_reply_no_id"};const s=y(),n=new ke(s.questionRequestsDir),r=await n.getRequest(i),a=Number(r?.created_at??0),l=a>0&&Date.now()-a>Fe;if(!r||r.resolved||l){o.warn("claude-adapter",`Question reply has no pending waiter: request_id=${i} exists=${!!r} resolved=${r?.resolved===!0} expired=${l}`);const v=this.pendingQuestion.get(i);return v&&this.activeEvent?.eventId===v.eventId&&(this.activeEvent.awaitingUserQuestion=!1),this.pendingQuestion.delete(i),this.bridgeCallbacks.sendLocalActionResult(e.action_id,"failed",void 0,"interaction_request_not_pending","The question is no longer pending; the reply was not delivered."),{handled:!0,kind:"question_reply_not_pending"}}const c=t.resolution??{},u=String(c.type??""),h=c;if(u==="action"&&String(c.value??"")==="cancel"){o.info("claude-adapter",`Question cancelled by user: request_id=${i}`),await n.resolveRequest(i,"cancel",void 0,h);const _=this.pendingQuestion.get(i);return _&&this.activeEvent?.eventId===_.eventId&&(this.activeEvent.awaitingUserQuestion=!1),this.pendingQuestion.delete(i),this.bridgeCallbacks.sendLocalActionResult(e.action_id,"ok",{request_id:i,resolution:"cancel"}),{handled:!0,kind:"question_reply_cancel"}}let p="";if(u==="text"?p=String(c.value??""):u==="map"&&(p=(Array.isArray(c.entries)?c.entries:[]).map(_=>_.value).join(", ")),!p){o.warn("claude-adapter",`Empty answer for question reply request_id=${i}`),await n.resolveRequest(i,"cancel",void 0,h);const v=this.pendingQuestion.get(i);return v&&this.activeEvent?.eventId===v.eventId&&(this.activeEvent.awaitingUserQuestion=!1),this.pendingQuestion.delete(i),this.bridgeCallbacks.sendLocalActionResult(e.action_id,"ok",{request_id:i,resolution:"cancel"}),{handled:!0,kind:"question_reply_empty"}}o.info("claude-adapter",`Resolving question in store: request_id=${i} answer=${p.slice(0,80)}`),await n.resolveRequest(i,"answer",p,h);const m=this.pendingQuestion.get(i);return m&&this.activeEvent?.eventId===m.eventId&&(this.activeEvent.awaitingUserQuestion=!1),this.pendingQuestion.delete(i),this.bridgeCallbacks.sendLocalActionResult(e.action_id,"ok",{request_id:i,resolution:"answer"}),{handled:!0,kind:"question_reply"}}async handlePermissionApproval(e){const t=e.params??{},i=String(t.approval_id??t.approval_command_id??t.tool_call_id??""),s=String(e.action_type??"")==="exec_approve";if(!i)return this.bridgeCallbacks.sendLocalActionResult(e.action_id,"failed",void 0,"approval_id_required","approval_id is required"),{handled:!0,kind:"permission_approval"};if(!this.pendingPermissions.get(i))return this.bridgeCallbacks.sendLocalActionResult(e.action_id,"failed",void 0,"approval_not_found",`no pending permission for approval_id: ${i}`),{handled:!0,kind:"permission_approval"};const r=y(),a=new G(r.permissionRequestsDir),l=s?"allow":"deny";return await a.resolveRequest(i,l),this.pendingPermissions.delete(i),this.scheduleTerminalPermissionKey(i,l),this.bridgeCallbacks.sendLocalActionResult(e.action_id,"ok"),o.info("claude-adapter",`Permission ${l}: approvalId=${i}`),{handled:!0,kind:"permission_approval"}}async handleExecApprovalEvent(e){const t=String(e.approval_id??e.approval_command_id??e.tool_call_id??"");if(!t)return{delivered:!1,errorCode:"approval_id_required",errorMsg:"approval_id is required"};if(!this.pendingPermissions.get(t))return{delivered:!1,errorCode:"approval_not_found",errorMsg:`no pending permission for approval_id: ${t}`};const i=e.decision==="deny"?"deny":"allow",s=y();return await new G(s.permissionRequestsDir).resolveRequest(t,i),this.pendingPermissions.delete(t),this.scheduleTerminalPermissionKey(t,i),o.info("claude-adapter",`Permission ${i} (text event): approvalId=${t}`),{delivered:!0}}async ensureClaudeAndPushEvent(e,t=0){t===0&&!this.claudeProcess&&this.startComposing(e.session_id,"preparing");try{if(this.mcpChannelBroken){await this.runSingleTurnFallback(e,"mcp channel unavailable");return}if(await this.isChannelGateClosed()){await this.runSingleTurnFallback(e,"channel gate closed: tengu_harbor not enabled in cachedGrowthBookFeatures");return}if(!this.getClaudeCliCapabilities(this.config.command||"claude").developmentChannels){await this.runSingleTurnFallback(e,"claude CLI does not support development channels");return}if(await this.ensureClaudeProcessReady(),await this.waitForChannelReady(),this.activeEvent?.eventId!==e.event_id||this.mcpChannelBroken)return;const s={},n=[["chat_id",e.session_id],["event_id",e.event_id],["event_type","user_chat"],["message_id",e.msg_id],["sender_id",e.sender_id],["user",e.sender_id],["user_id",e.sender_id],["msg_id",e.msg_id],["session_type",e.session_type!=null?String(e.session_type):void 0],["msg_type",e.msg_type!=null?String(e.msg_type):void 0],["quoted_message_id",e.quoted_message_id],["ts",e.created_at!=null?new Date(Number(e.created_at)).toISOString():void 0],["owner_id",e.owner_id],["agent_id",this.bridgeCallbacks.getAgentId?.()],["attachments_json",e.attachments_json],["context_messages_json",it(e.context_messages_json)??e.context_messages_json]];for(const[a,l]of n)l!=null&&l!==""&&(s[a]=l);this.captureEventJsonlBaseOffset(e.event_id,this.activeEvent?.textForwardOffset===void 0?{force:!0}:void 0);const r=e.content??"";this.pushNotification("notifications/claude/channel",{content:r,meta:s}),this.activeEvent?.eventId===e.event_id&&(this.activeEvent.channelPushedAt=Date.now()),o.info("claude-adapter",`Event ${e.event_id} pushed to MCP stdio server`)}catch(s){if(this.stopped){this.activeEvent?.eventId===e.event_id&&(this.bridgeCallbacks.sendEventResult(e.event_id,"canceled","adapter stopped"),this.clearActiveEvent());return}if(s instanceof Error&&(s.message.includes("exited before")||s.message.includes("timeout")||s.message.includes("spawn failed"))&&t<3)return o.warn("claude-adapter",`Spawn failed (attempt ${t+1}/4), retrying: ${s}`),this.claudeProcess=null,this.claudeChildPid=0,this.alive=!1,this.releaseNotifyPortReservation(),this.stopMcpServer(),me(this.claudeCliSessionId,[]),await new Promise(a=>setTimeout(a,(t+1)*2e3)),this.ensureClaudeAndPushEvent(e,t+1);o.error("claude-adapter",`Failed to deliver event ${e.event_id}: ${s}`);let r=s;if(this.shouldFallbackToSingleTurn(s)){o.warn("claude-adapter",`Channel path failed, switching to single-turn fallback: ${s instanceof Error?s.message:String(s)}`);try{await this.runSingleTurnFallback(e,s instanceof Error?s.message:String(s));return}catch(a){o.error("claude-adapter",`Single-turn fallback failed for ${e.event_id}: ${a instanceof Error?a.message:String(a)}`),r=a}}this.activeEvent?.eventId===e.event_id&&(this.bridgeCallbacks.sendEventResult(e.event_id,"failed",r instanceof Error?r.message:String(r)),this.clearActiveEvent())}}shouldFallbackToSingleTurn(e){const t=(e instanceof Error?e.message:String(e)).toLowerCase();return t?t.includes("mcp server startup failed")||t.includes("claude channel listener not ready")||t.includes("channel ready check timed out")||t.includes("channels are not currently available")||t.includes("does not support development channels")||t.includes("unknown option")||t.includes("unknown or unexpected option")||t.includes("mcp stdio server not available")||/notify port\b.*\bnot ready/.test(t)||t.includes("file not found:"):!1}getClaudeCliCapabilities(e){return this.claudeCliCapabilities?this.claudeCliCapabilities:(this.claudeCliCapabilities=ji(e),this.claudeCliCapabilities)}buildSingleTurnPrompt(e,t=!0){const i=t&&e.context_messages_json?it(e.context_messages_json)??String(e.context_messages_json):"",s=[e.content??""];return i&&(s.push("","Conversation context (JSON):"),s.push(i)),s.join(`
4
+ `)}finally{gt(s)}}handleStatusLineUpdate(e){try{const t=e.context_window,i=e.cost,s=e.model,n=e.rate_limits??e.rateLimits,r=t?.current_usage;if(this.sessionState={contextWindow:{usedPercentage:t?.used_percentage!=null?w(t.used_percentage):null,remainingPercentage:t?.remaining_percentage!=null?w(t.remaining_percentage):null,totalInputTokens:w(t?.total_input_tokens),totalOutputTokens:w(t?.total_output_tokens),contextWindowSize:w(t?.context_window_size)||2e5,currentUsage:r?{inputTokens:w(r.input_tokens),outputTokens:w(r.output_tokens),cacheCreationInputTokens:w(r.cache_creation_input_tokens),cacheReadInputTokens:w(r.cache_read_input_tokens)}:null},cost:{totalCostUsd:w(i?.total_cost_usd),totalDurationMs:w(i?.total_duration_ms),totalApiDurationMs:w(i?.total_api_duration_ms),totalLinesAdded:w(i?.total_lines_added),totalLinesRemoved:w(i?.total_lines_removed)},rateLimits:this.parseStatusRateLimits(n),model:{id:String(s?.id??""),displayName:String(s?.display_name??"")},fastMode:e.fast_mode===!0,effort:e.effort!=null?String(e.effort):void 0,thinkingEnabled:e.thinking_enabled===!0,exceeds200kTokens:e.exceeds_200k_tokens===!0,version:String(e.version??""),sampledAt:Date.now()},n){const a=this.sessionState.rateLimits?.fiveHour,l=this.sessionState.rateLimits?.sevenDay;o.info("claude-adapter",`[rate-limits] statusLine parsed: fiveHour=${a?`${a.usedPercentage}% resetsAt=${a.resetsAt}`:"n/a"} sevenDay=${l?`${l.usedPercentage}% resetsAt=${l.resetsAt}`:"n/a"}`)}else o.debug("claude-adapter","[rate-limits] statusLine: no rate_limits in payload");try{this.bridgeCallbacks.onStatusLineUpdated?.(this.sessionState)}catch(a){o.warn("claude-adapter",`onStatusLineUpdated callback error: ${a instanceof Error?a.message:a}`)}}catch(t){o.warn("claude-adapter",`Failed to parse statusLine payload: ${t instanceof Error?t.message:t}`)}}parseStatusRateLimits(e){if(!e)return;const t=n=>{if(!n||typeof n!="object")return;const r=n,a=w(r.used_percentage??r.usedPercent),l=w(r.resets_at??r.resetsAt);if(!(a<=0&&l<=0))return{usedPercentage:a,resetsAt:l}},i=t(e.five_hour??e.fiveHour),s=t(e.seven_day??e.sevenDay);if(!(!i&&!s))return{...i?{fiveHour:i}:{},...s?{sevenDay:s}:{}}}deliverInboundEvent(e){if(this.sessionId||(this.sessionId=e.session_id),this.pruneCompletedEvents(),this.completedEventIds.has(e.event_id)){o.info("claude-adapter",`Event ${e.event_id} rejected: duplicate`),this.bridgeCallbacks.sendEventResult(e.event_id,"responded","duplicate event");return}if(Date.now()<this.authFailureUntil){o.info("claude-adapter",`Event ${e.event_id} rejected: auth cooldown`),this.bridgeCallbacks.sendEventResult(e.event_id,"failed","Claude authentication failed \u2014 please re-login");return}if(Date.now()<this.usageLimitUntil){o.info("claude-adapter",`Event ${e.event_id} rejected: usage limit cooldown`),this.bridgeCallbacks.sendEventResult(e.event_id,"failed","Claude usage limit reached \u2014 waiting for reset");return}if(this.activeEvent?.eventId===e.event_id){o.info("claude-adapter",`Event ${e.event_id} ignored: same as active event`);return}if(this.activeEvent||this.compacting||this.stopHookBarrierSessionId===e.session_id){o.error("claude-adapter",`Scheduler invariant violated: event ${e.event_id} delivered while busy (active=${this.activeEvent?.eventId??"none"}, compacting=${this.compacting}, barrier=${this.stopHookBarrierSessionId===e.session_id}) \u2014 failing event to release slot`),this.bridgeCallbacks.sendEventResult(e.event_id,"failed","agent busy (scheduler invariant violated)");return}this.activeEvent={eventId:e.event_id,sessionId:e.session_id,rawEvent:e},this.stopSelfDriven(),this.clearSelfDrivenDeliverTimer(),this.lastClearedEvent=null,this.markActiveEventActivity(e.event_id,e.session_id),this.resetActiveEventHardTimer(e.event_id),this.emit("eventStarted",e.event_id,e.session_id),this.ensureClaudeAndPushEvent(e)}deliverStopEvent(e,t){if(this.activeEvent?.eventId!==e){o.info("claude-adapter",`Stop for non-active event=${e} (active=${this.activeEvent?.eventId??"none"}, compacting=${this.compacting}) \u2014 emitting eventDone to unblock platform`),this.emit("eventDone",e);return}const i=this.activeEvent.sessionId;o.info("claude-adapter",`Stop requested for active event=${e} \u2014 killing Claude process`),this.mcpServerReady&&this.pushNotification("notifications/event_stop",{event_id:e,session_id:i,stop_id:J()}),(this.claudeProcess||this.claudePty)&&this.emit("pauseIntake","restart"),this.bridgeCallbacks.sendEventResult(e,"canceled","stopped by user"),this.clearActiveEvent(),this.killClaudeProcess("stop")}async handleLocalAction(e){if(!this.mcpServerReady)return{handled:!1,kind:""};const t=String(e.action_type??"");if(t==="claude_interaction_reply"){const i=e.params??{};if(String(i.kind??"")==="permission"){const n=i.resolution??{},r=String(n.value??""),a={...e,action_type:r==="allow"?"exec_approve":"exec_reject",params:{...i,approval_command_id:i.request_id}};return this.handlePermissionApproval(a)}return this.handleQuestionReply(e)}return t==="exec_approve"||t==="exec_reject"?this.handlePermissionApproval(e):(this.pushNotification("notifications/local_action",e),{handled:!0,kind:""})}async handleQuestionReply(e){const t=e.params??{},i=String(t.request_id??"");if(!i)return o.warn("claude-adapter","Question reply missing request_id"),this.bridgeCallbacks.sendLocalActionResult(e.action_id,"failed",void 0,"request_id_required","request_id is required"),{handled:!0,kind:"question_reply_no_id"};const s=y(),n=new ke(s.questionRequestsDir),r=await n.getRequest(i),a=Number(r?.created_at??0),l=a>0&&Date.now()-a>Fe;if(!r||r.resolved||l){o.warn("claude-adapter",`Question reply has no pending waiter: request_id=${i} exists=${!!r} resolved=${r?.resolved===!0} expired=${l}`);const v=this.pendingQuestion.get(i);return v&&this.activeEvent?.eventId===v.eventId&&(this.activeEvent.awaitingUserQuestion=!1),this.pendingQuestion.delete(i),this.bridgeCallbacks.sendLocalActionResult(e.action_id,"failed",void 0,"interaction_request_not_pending","The question is no longer pending; the reply was not delivered."),{handled:!0,kind:"question_reply_not_pending"}}const c=t.resolution??{},u=String(c.type??""),h=c;if(u==="action"&&String(c.value??"")==="cancel"){o.info("claude-adapter",`Question cancelled by user: request_id=${i}`),await n.resolveRequest(i,"cancel",void 0,h);const _=this.pendingQuestion.get(i);return _&&this.activeEvent?.eventId===_.eventId&&(this.activeEvent.awaitingUserQuestion=!1),this.pendingQuestion.delete(i),this.bridgeCallbacks.sendLocalActionResult(e.action_id,"ok",{request_id:i,resolution:"cancel"}),{handled:!0,kind:"question_reply_cancel"}}let p="";if(u==="text"?p=String(c.value??""):u==="map"&&(p=(Array.isArray(c.entries)?c.entries:[]).map(_=>_.value).join(", ")),!p){o.warn("claude-adapter",`Empty answer for question reply request_id=${i}`),await n.resolveRequest(i,"cancel",void 0,h);const v=this.pendingQuestion.get(i);return v&&this.activeEvent?.eventId===v.eventId&&(this.activeEvent.awaitingUserQuestion=!1),this.pendingQuestion.delete(i),this.bridgeCallbacks.sendLocalActionResult(e.action_id,"ok",{request_id:i,resolution:"cancel"}),{handled:!0,kind:"question_reply_empty"}}o.info("claude-adapter",`Resolving question in store: request_id=${i} answer=${p.slice(0,80)}`),await n.resolveRequest(i,"answer",p,h);const m=this.pendingQuestion.get(i);return m&&this.activeEvent?.eventId===m.eventId&&(this.activeEvent.awaitingUserQuestion=!1),this.pendingQuestion.delete(i),this.bridgeCallbacks.sendLocalActionResult(e.action_id,"ok",{request_id:i,resolution:"answer"}),{handled:!0,kind:"question_reply"}}async handlePermissionApproval(e){const t=e.params??{},i=String(t.approval_id??t.approval_command_id??t.tool_call_id??""),s=String(e.action_type??"")==="exec_approve";if(!i)return this.bridgeCallbacks.sendLocalActionResult(e.action_id,"failed",void 0,"approval_id_required","approval_id is required"),{handled:!0,kind:"permission_approval"};if(!this.pendingPermissions.get(i))return this.bridgeCallbacks.sendLocalActionResult(e.action_id,"failed",void 0,"approval_not_found",`no pending permission for approval_id: ${i}`),{handled:!0,kind:"permission_approval"};const r=y(),a=new z(r.permissionRequestsDir),l=s?"allow":"deny";return await a.resolveRequest(i,l),this.pendingPermissions.delete(i),this.scheduleTerminalPermissionKey(i,l),this.bridgeCallbacks.sendLocalActionResult(e.action_id,"ok"),o.info("claude-adapter",`Permission ${l}: approvalId=${i}`),{handled:!0,kind:"permission_approval"}}async handleExecApprovalEvent(e){const t=String(e.approval_id??e.approval_command_id??e.tool_call_id??"");if(!t)return{delivered:!1,errorCode:"approval_id_required",errorMsg:"approval_id is required"};if(!this.pendingPermissions.get(t))return{delivered:!1,errorCode:"approval_not_found",errorMsg:`no pending permission for approval_id: ${t}`};const i=e.decision==="deny"?"deny":"allow",s=y();return await new z(s.permissionRequestsDir).resolveRequest(t,i),this.pendingPermissions.delete(t),this.scheduleTerminalPermissionKey(t,i),o.info("claude-adapter",`Permission ${i} (text event): approvalId=${t}`),{delivered:!0}}async ensureClaudeAndPushEvent(e,t=0){t===0&&!this.claudeProcess&&this.startComposing(e.session_id,"preparing");try{if(this.mcpChannelBroken){await this.runSingleTurnFallback(e,"mcp channel unavailable");return}if(await this.isChannelGateClosed()){await this.runSingleTurnFallback(e,"channel gate closed: tengu_harbor not enabled in cachedGrowthBookFeatures");return}if(!this.getClaudeCliCapabilities(this.config.command||"claude").developmentChannels){await this.runSingleTurnFallback(e,"claude CLI does not support development channels");return}if(await this.ensureClaudeProcessReady(),await this.waitForChannelReady(),this.activeEvent?.eventId!==e.event_id||this.mcpChannelBroken)return;const s={},n=[["chat_id",e.session_id],["event_id",e.event_id],["event_type","user_chat"],["message_id",e.msg_id],["sender_id",e.sender_id],["user",e.sender_id],["user_id",e.sender_id],["msg_id",e.msg_id],["session_type",e.session_type!=null?String(e.session_type):void 0],["msg_type",e.msg_type!=null?String(e.msg_type):void 0],["quoted_message_id",e.quoted_message_id],["ts",e.created_at!=null?new Date(Number(e.created_at)).toISOString():void 0],["owner_id",e.owner_id],["agent_id",this.bridgeCallbacks.getAgentId?.()],["attachments_json",e.attachments_json],["context_messages_json",it(e.context_messages_json)??e.context_messages_json]];for(const[a,l]of n)l!=null&&l!==""&&(s[a]=l);this.captureEventJsonlBaseOffset(e.event_id,this.activeEvent?.textForwardOffset===void 0?{force:!0}:void 0);const r=e.content??"";this.pushNotification("notifications/claude/channel",{content:r,meta:s}),this.activeEvent?.eventId===e.event_id&&(this.activeEvent.channelPushedAt=Date.now()),o.info("claude-adapter",`Event ${e.event_id} pushed to MCP stdio server`)}catch(s){if(this.stopped){this.activeEvent?.eventId===e.event_id&&(this.bridgeCallbacks.sendEventResult(e.event_id,"canceled","adapter stopped"),this.clearActiveEvent());return}if(s instanceof Error&&(s.message.includes("exited before")||s.message.includes("timeout")||s.message.includes("spawn failed"))&&t<3)return o.warn("claude-adapter",`Spawn failed (attempt ${t+1}/4), retrying: ${s}`),this.claudeProcess=null,this.claudeChildPid=0,this.alive=!1,this.releaseNotifyPortReservation(),this.stopMcpServer(),me(this.claudeCliSessionId,[]),await new Promise(a=>setTimeout(a,(t+1)*2e3)),this.ensureClaudeAndPushEvent(e,t+1);o.error("claude-adapter",`Failed to deliver event ${e.event_id}: ${s}`);let r=s;if(this.shouldFallbackToSingleTurn(s)){o.warn("claude-adapter",`Channel path failed, switching to single-turn fallback: ${s instanceof Error?s.message:String(s)}`);try{await this.runSingleTurnFallback(e,s instanceof Error?s.message:String(s));return}catch(a){o.error("claude-adapter",`Single-turn fallback failed for ${e.event_id}: ${a instanceof Error?a.message:String(a)}`),r=a}}this.activeEvent?.eventId===e.event_id&&(this.bridgeCallbacks.sendEventResult(e.event_id,"failed",r instanceof Error?r.message:String(r)),this.clearActiveEvent())}}shouldFallbackToSingleTurn(e){const t=(e instanceof Error?e.message:String(e)).toLowerCase();return t?t.includes("mcp server startup failed")||t.includes("claude channel listener not ready")||t.includes("channel ready check timed out")||t.includes("channels are not currently available")||t.includes("does not support development channels")||t.includes("unknown option")||t.includes("unknown or unexpected option")||t.includes("mcp stdio server not available")||/notify port\b.*\bnot ready/.test(t)||t.includes("file not found:"):!1}getClaudeCliCapabilities(e){return this.claudeCliCapabilities?this.claudeCliCapabilities:(this.claudeCliCapabilities=ji(e),this.claudeCliCapabilities)}buildSingleTurnPrompt(e,t=!0){const i=t&&e.context_messages_json?it(e.context_messages_json)??String(e.context_messages_json):"",s=[e.content??""];return i&&(s.push("","Conversation context (JSON):"),s.push(i)),s.join(`
5
5
  `)}extractSingleTurnDelta(e){const t=e.trim();if(!t)return null;let i;try{i=JSON.parse(t)}catch{return o.warn("claude-adapter",`Single-turn fallback: dropping non-JSON stdout noise: ${t.slice(0,200)}`),null}const s=i.type==="stream_event"&&i.event&&typeof i.event=="object"?i.event:i,n=String(s.type??"");if(n==="content_block_delta"){const r=s.delta;if(r&&typeof r=="object"){const a=String(r.text??"");if(a)return{text:a,final:!1}}return null}if(n==="result"){const r=String(s.subtype??""),a=String(s.terminal_reason??""),l=s.is_error===!0||a==="api_error";if(r&&r!=="success"||l){const u=Array.isArray(s.errors)?s.errors.join("; "):"",h=String(s.result??"").trim(),p=u||h||a;throw new Error(`claude single-turn result ${r||"error"}${p?`: ${p}`:""}`)}const c=String(s.result??"");return c?{text:c,final:!0}:null}return null}resolveEventAuditConfig(e,t){if(e.audit?.enabled===!0)return e.audit;const i=t?.audit;if(i?.enabled!==!0)return;const s=i.profile??"full";return{enabled:!0,profile:s,capture:i.capture??(s==="usage"?{inputOutput:!1,tools:!1,subagents:!0,thinking:!1,rawProviderBody:i.rawProviderBody===!0}:{inputOutput:!0,tools:!0,subagents:!0,thinking:!1,rawProviderBody:i.rawProviderBody===!0})}}buildClaudeRuntimeEnv(e=this.resolveSessionRuntime()){const t={...V(),...this.config.env};if(delete t.CLAUDECODE,t.CLAUDE_PLUGIN_DATA=y().dataDir,t.GRIX_HOOK_SIGNALS_PATH=this.resolveHookSignalsPath(),this.internalApi&&(t.GRIX_CLAUDE_INTERNAL_API_URL=this.internalApi.baseUrl,t.GRIX_CLAUDE_INTERNAL_API_TOKEN=this.internalApi.token),e.audit?.enabled===!0&&!this.internalApi&&o.error("claude-adapter","Audit enabled but internal API not started; OTEL capture env will not be injected"),e.audit?.enabled===!0&&this.internalApi){const s=this.ensureClaudeAuditEvidence(e.audit);t.CLAUDE_CODE_ENABLE_TELEMETRY="1",t.CLAUDE_CODE_ENHANCED_TELEMETRY_BETA="1",t.OTEL_LOGS_EXPORTER="otlp",t.OTEL_TRACES_EXPORTER="otlp",t.OTEL_METRICS_EXPORTER="none",t.OTEL_EXPORTER_OTLP_PROTOCOL="http/json",t.OTEL_EXPORTER_OTLP_ENDPOINT=this.internalApi.baseUrl,t.OTEL_EXPORTER_OTLP_HEADERS=`Authorization=Bearer ${this.internalApi.token}`,t.OTEL_LOGS_EXPORT_INTERVAL="1000",t.OTEL_TRACES_EXPORT_INTERVAL="1000",t.OTEL_LOG_USER_PROMPTS=e.audit.capture.inputOutput?"1":"0",t.OTEL_LOG_ASSISTANT_RESPONSES=e.audit.capture.inputOutput?"1":"0",t.OTEL_LOG_TOOL_DETAILS=e.audit.capture.tools?"1":"0",t.OTEL_LOG_TOOL_CONTENT=e.audit.capture.tools?"1":"0",e.audit.capture.rawProviderBody&&(t.OTEL_LOG_RAW_API_BODIES=`file:${s.rawBodyDir}`)}const i=Tt(e.effort);if(i==="auto")delete t.CLAUDE_CODE_EFFORT_LEVEL;else{const s=At(i);s&&(t.CLAUDE_CODE_EFFORT_LEVEL=s)}return t}ensureClaudeAuditEvidence(e){if(this.auditEvidencePaths)return this.auditEvidencePaths;const t=this.config.options??{},s=String(t.auditEvidenceRoot??"").trim()||f(y().dataDir,"audit-evidence"),n=qe(this.sessionId),r=qe(this.claudeCliSessionId||"pending"),a=f(s,"claude",n,r),l=f(a,"raw-api-bodies");Ce(l,{recursive:!0,mode:448}),ae(a,448),ae(l,448);const c={rootDir:a,logsPath:f(a,"otel-logs.jsonl"),tracesPath:f(a,"otel-traces.jsonl"),rawBodyDir:l,rawProviderBody:e.capture.rawProviderBody};return this.internalApi?.setOtelHandler(async(u,h,p)=>{const m=u==="logs"?c.logsPath:c.tracesPath;await _t(m,`${JSON.stringify({observedAt:new Date().toISOString(),signal:u,payload:h,rawBody:p})}
6
- `,{encoding:"utf8",mode:384}),ae(m,384)}),this.auditEvidencePaths=c,c}async runSingleTurnFallback(e,t){const i=this.resolveSessionRuntime(),s=this.resolveEventAuditConfig(i,e);if(s&&!this.internalApi){o.error("claude-adapter",`Audit capture unavailable for event ${e.event_id}: internal API not started`),this.bridgeCallbacks.sendEventResult(e.event_id,"failed","audit capture unavailable: internal API not started","audit_capture_unavailable");return}if(this.activeEvent?.eventId===e.event_id&&(this.activeEvent.singleTurnFallback=!0,s&&(this.activeEvent.auditSingleTurnFallback=!0)),this.claudeProcess||this.claudePty){const x=this.mcpChannelBroken,g=this.channelGateClosed;this.killClaudeProcess("single-turn fallback isolation"),this.mcpChannelBroken=x,this.channelGateClosed=g}const n=this.config.options??{},r=this.config.command||"claude",a=(this.config.args??[]).filter(x=>{const g=String(x).trim().toLowerCase();return g!=="--dangerously-load-development-channels"&&g!=="--session-id"&&g!=="--resume"}),l=String(i.cwd??"").trim(),c=String(i.modelId??"").trim(),u=tt(i.modeId);await this.ensureWorkspaceTrust(l),await this.ensureClaudeOnboardingFlags(l),await this.ensureSkipDangerousPermissionPrompt(),await this.injectStatusLineSettings(l);const h=String(i.claudeSessionId??"").trim()||this.claudeCliSessionId||J();this.claudeCliSessionId=h,this.claudeSessionCwd=l,!i.claudeSessionId&&i.onSessionIdAssigned&&i.onSessionIdAssigned(h),await this.ensureStdioMcpServer();const p=this.buildClaudeRuntimeEnv(s?{...i,audit:s}:i),m=i.pluginDir??n.pluginDir??await this.ensureClaudePluginDir();let v=at(h,l||void 0);v&&ot(h,l||void 0)&&(v=!1);const _=String(i.claudeSessionId??"").trim()!==""&&!v;v&&lt(h,l||void 0),this.captureSingleTurnFallbackJsonlBaseOffset(e.event_id),_&&o.warn("claude-adapter",`Claude local transcript missing for session ${h}; continuing silently with platform context`);const F=this.buildSingleTurnPrompt(e,!v),P=[...a,"-p","--output-format","stream-json","--verbose","--include-partial-messages",...v?["--resume",h]:["--session-id",h]];P.push("--plugin-dir",m,"--strict-mcp-config","--mcp-config",this.claudeMcpConfigPath,"--permission-mode",u===z.fullAuto?"bypassPermissions":"default"),c&&P.push("--model",c),P.push(F),o.warn("claude-adapter",`Running single-turn fallback for event ${e.event_id}: ${t}`),this.bridgeCallbacks.sendEventAck(e.event_id,e.session_id),this.startComposing(e.session_id,"fallback_single_turn"),await new Promise((x,g)=>{const N=ye(r,P),E=se(N.command,N.args,{cwd:l||process.cwd(),env:p,stdio:["ignore","pipe","pipe"],detached:process.platform!=="win32",windowsHide:!0,windowsVerbatimArguments:N.windowsVerbatimArguments});this.singleTurnFallbackChild=E;const b=()=>{this.singleTurnFallbackChild===E&&(this.singleTurnFallbackChild=null)};let A="",j=0,U=!1,ie=null;const Ee=`fallback_${e.event_id}_${Date.now()}`,_e=String(e.msg_id??"").trim()||void 0;let $="";const Se=R=>{let C;try{C=this.extractSingleTurnDelta(R)}catch(H){ie=H instanceof Error?H:new Error(String(H));return}C&&(C.final&&U||this.activeEvent?.eventId===e.event_id&&this.isEventAnswered()||(j+=1,U=!0,this.markActiveEventActivity(e.event_id,e.session_id),this.bridgeCallbacks.sendStreamChunk(e.event_id,e.session_id,C.text,j,!1,Ee,_e)))};E.stdout?.setEncoding("utf8"),E.stdout?.on("data",R=>{$+=R;let C=$.indexOf(`
6
+ `,{encoding:"utf8",mode:384}),ae(m,384)}),this.auditEvidencePaths=c,c}async runSingleTurnFallback(e,t){const i=this.resolveSessionRuntime(),s=this.resolveEventAuditConfig(i,e);if(s&&!this.internalApi){o.error("claude-adapter",`Audit capture unavailable for event ${e.event_id}: internal API not started`),this.bridgeCallbacks.sendEventResult(e.event_id,"failed","audit capture unavailable: internal API not started","audit_capture_unavailable");return}if(this.activeEvent?.eventId===e.event_id&&(this.activeEvent.singleTurnFallback=!0,s&&(this.activeEvent.auditSingleTurnFallback=!0)),this.claudeProcess||this.claudePty){const x=this.mcpChannelBroken,g=this.channelGateClosed;this.killClaudeProcess("single-turn fallback isolation"),this.mcpChannelBroken=x,this.channelGateClosed=g}const n=this.config.options??{},r=this.config.command||"claude",a=(this.config.args??[]).filter(x=>{const g=String(x).trim().toLowerCase();return g!=="--dangerously-load-development-channels"&&g!=="--session-id"&&g!=="--resume"}),l=String(i.cwd??"").trim(),c=String(i.modelId??"").trim(),u=tt(i.modeId);await this.ensureWorkspaceTrust(l),await this.ensureClaudeOnboardingFlags(l),await this.ensureSkipDangerousPermissionPrompt(),await this.injectStatusLineSettings(l);const h=String(i.claudeSessionId??"").trim()||this.claudeCliSessionId||J();this.claudeCliSessionId=h,this.claudeSessionCwd=l,!i.claudeSessionId&&i.onSessionIdAssigned&&i.onSessionIdAssigned(h),await this.ensureStdioMcpServer();const p=this.buildClaudeRuntimeEnv(s?{...i,audit:s}:i);p.GRIX_CLAUDE_SINGLE_TURN="1";const m=i.pluginDir??n.pluginDir??await this.ensureClaudePluginDir();let v=at(h,l||void 0);v&&ot(h,l||void 0)&&(v=!1);const _=String(i.claudeSessionId??"").trim()!==""&&!v;v&&lt(h,l||void 0),this.captureSingleTurnFallbackJsonlBaseOffset(e.event_id),_&&o.warn("claude-adapter",`Claude local transcript missing for session ${h}; continuing silently with platform context`);const F=this.buildSingleTurnPrompt(e,!v),P=[...a,"-p","--output-format","stream-json","--verbose","--include-partial-messages",...v?["--resume",h]:["--session-id",h]];P.push("--plugin-dir",m,"--strict-mcp-config","--mcp-config",this.claudeMcpConfigPath,"--permission-mode",u===G.fullAuto?"bypassPermissions":"default"),c&&P.push("--model",c),P.push(F),o.warn("claude-adapter",`Running single-turn fallback for event ${e.event_id}: ${t}`),this.bridgeCallbacks.sendEventAck(e.event_id,e.session_id),this.startComposing(e.session_id,"fallback_single_turn"),await new Promise((x,g)=>{const N=ye(r,P),E=se(N.command,N.args,{cwd:l||process.cwd(),env:p,stdio:["ignore","pipe","pipe"],detached:process.platform!=="win32",windowsHide:!0,windowsVerbatimArguments:N.windowsVerbatimArguments});this.singleTurnFallbackChild=E;const b=()=>{this.singleTurnFallbackChild===E&&(this.singleTurnFallbackChild=null)};let A="",j=0,U=!1,ie=null;const Ee=`fallback_${e.event_id}_${Date.now()}`,_e=String(e.msg_id??"").trim()||void 0;let $="";const Se=R=>{let C;try{C=this.extractSingleTurnDelta(R)}catch(H){ie=H instanceof Error?H:new Error(String(H));return}C&&(C.final&&U||this.activeEvent?.eventId===e.event_id&&this.isEventAnswered()||(j+=1,U=!0,this.markActiveEventActivity(e.event_id,e.session_id),this.bridgeCallbacks.sendStreamChunk(e.event_id,e.session_id,C.text,j,!1,Ee,_e)))};E.stdout?.setEncoding("utf8"),E.stdout?.on("data",R=>{$+=R;let C=$.indexOf(`
7
7
  `);for(;C>=0;){const H=$.slice(0,C);$=$.slice(C+1),Se(H),C=$.indexOf(`
8
- `)}}),E.stderr?.setEncoding("utf8"),E.stderr?.on("data",R=>{A.length>=he||(A+=R,A.length>he&&(A=A.slice(0,he)))}),E.on("error",R=>{b(),g(R)}),E.on("close",R=>{if(b(),$.trim()&&Se($),ie){g(ie);return}if(R!==0&&!U){const C=A.trim();g(new Error(C||`claude single-turn exited with code ${R}`));return}if(!U&&this.activeEvent?.eventId===e.event_id&&!this.isEventAnswered()){const C=A.trim();g(new Error(C||"claude single-turn produced no output"));return}U&&(j+=1,this.bridgeCallbacks.sendStreamChunk(e.event_id,e.session_id,"",j,!0,Ee,_e)),x()})}),this.activeEvent?.eventId===e.event_id&&(this.bridgeCallbacks.sendEventResult(e.event_id,"responded"),this.clearActiveEvent())}async waitForChannelReady(){if(!this.mcpServerReady)throw new Error("MCP stdio server not available");await this.waitForNotifyPort(3e4),process.platform==="win32"?await this.waitForWindowsChannelReady(Xe):await this.waitForChannelListening(Xe)}async ensureClaudeProcessReady(){this.claudeProcess||this.claudePty||(this.spawnPromise||(this.spawnPromise=this.spawnClaude().finally(()=>{this.spawnPromise=null})),await this.spawnPromise)}async spawnClaude(){if(this.claudeProcess)return;const e=this.lifecycleVersion,t=()=>{if(this.stopped||e!==this.lifecycleVersion)throw new Error("adapter stopped")},i=this.config.options??{};this.sessionIdConflictDetected=!1;const s=this.resolveSessionRuntime(),n=this.config.command||"claude",r=this.config.args??[],a=tt(s.modeId),l=String(s.modelId??"").trim(),c=String(s.cwd??"").trim();this.claudeSessionCwd=c;const u=String(s.claudeSessionId??"").trim()||J();this.claudeCliSessionId=u,!s.claudeSessionId&&s.onSessionIdAssigned&&s.onSessionIdAssigned(u);const h=this.buildClaudeRuntimeEnv((()=>{const P=this.resolveEventAuditConfig(s,this.activeEvent?.rawEvent);return P?{...s,audit:P}:s})());let p=at(u,c||void 0);if(p&&ot(u,c||void 0)&&(p=!1),String(s.claudeSessionId??"").trim()!==""&&!p&&o.warn("claude-adapter",`Claude local transcript missing for session ${u}; continuing silently with platform context`),p?lt(u,c||void 0):(me(u,[]),ve(u)),this.runtimeResolver&&!c)throw new Error("Claude session binding missing cwd \u2014 run /grix open <working-directory> first");if(!c)throw new Error("Claude runtime cwd is required");await this.ensureWorkspaceTrust(c),await this.ensureClaudeOnboardingFlags(c),await this.ensureSkipDangerousPermissionPrompt(),await this.injectStatusLineSettings(c);let m=!1,v="",_=null;try{t(),this.notifyPort=await Ci([this.internalApiPort]),m=!0,t(),o.info("claude-adapter",`Allocated MCP notify port ${this.notifyPort} (internal API ${this.internalApiPort})`),await this.ensureStdioMcpServer(),t();const P=i.pluginDir||await this.ensureClaudePluginDir(),x=p?["--resume",u]:["--session-id",u],g=[...r,"--name",`grix-${this.sessionId}`,...x,"--plugin-dir",P,"--strict-mcp-config","--mcp-config",this.claudeMcpConfigPath];l&&(/^claude/i.test(l)?g.push("--model",l):(o.warn("claude-adapter",`Skipping --model ${l}: non-Anthropic models are incompatible with development channels. Using default model so channels can load. Switch models after startup via /model if needed.`),this.deferredModelId=l)),a===z.fullAuto&&g.push("--dangerously-skip-permissions"),g.push("--dangerously-load-development-channels",`server:${M}`);const N=Ii(this.bridgeCallbacks.getAgentProfile?.(),p?void 0:this.sessionId,this.bridgeCallbacks.getAgentId?.());if(process.platform==="win32"){const E=N.replace(/</g,"[").replace(/>/g,"]").replace(/"/g,"'"),b=Ni(this.claudeCliSessionId);await Hi(b,E),g.push("--append-system-prompt-file",b)}else g.push("--append-system-prompt",N);if(process.platform==="win32")if(Y){const E=qi(),b=["/c",n,...g];o.info("claude-adapter",`Spawning Claude via PTY on win32: cwd=${c} mode=${a} ${p?"resume":"new"} ${n} ${g.join(" ")}`);const A=Y.spawn(E,b,{name:"xterm-256color",cols:120,rows:30,cwd:c,env:h,useConpty:!0,conptyInheritCursor:!1});this.claudePty=A,this.claudeChildPid=A.pid,_=null}else o.info("claude-adapter",`Spawning Claude via shell on win32 (node-pty unavailable): cwd=${c} mode=${a} ${p?"resume":"new"} ${n} ${g.join(" ")}`),_=se(n,g,{cwd:c,env:h,stdio:["pipe","pipe","pipe"],detached:process.platform!=="win32",shell:!0,windowsHide:!0});else{o.info("claude-adapter",`Spawning via expect PTY: cwd=${c} mode=${a} ${p?"resume":"new"} ${n} ${g.join(" ")}`);const E=Zt(typeof h.PATH=="string"?h.PATH:void 0);if(!E)throw new Error("expect not found in PATH. Install it with: apt install expect / dnf install expect / brew install expect");const b=f(Pt(),`grix-claude-${J()}`);await L(b,{recursive:!0}),this.expectRunDir=b;const{expectPath:A,pidPath:j}=await xi(b,n,g);v=j,t(),_=se(E,[A],{cwd:c,env:h,stdio:["pipe","pipe","pipe"],detached:!0})}if(this.claudeProcess=_,this.clearPendingMcpFailureTimer(),this.mcpStartupFailureHandled=!1,this.mcpChannelBroken=!1,this.channelGateClosed=!1,this.startupChannelListening=!1,this.startupChannelListeningAt=0,this.startupDialogSeenAt=0,this.clearWindowsTrustDialogTimer(),this.windowsTrustDialogAnswered=!1,this.clearDevChannelsConfirmTimer(),this.ptyDialogBusy=!1,_&&_.on("error",E=>{o.error("claude-adapter","Claude process spawn error: "+E),this.claudeProcess=null,this.claudeChildPid=0,this.alive=!1,this.releaseNotifyPortReservation(),this.stopMcpServer(),this.clearPendingMcpFailureTimer(),this.activeEvent&&(this.bridgeCallbacks.sendEventResult(this.activeEvent.eventId,"failed","Claude process spawn failed"),this.clearActiveEvent())}),t(),!this.claudePty)if(process.platform==="win32")this.claudeChildPid=_?.pid??0;else{const E=await Mi(_,v);if(t(),!Number.isFinite(E)||E<=0)throw new Error("failed to determine spawned Claude pid");this.claudeChildPid=E}if(!this.claudeChildPid||this.claudeChildPid<=0)throw new Error("failed to determine spawned Claude pid")}catch(P){if(_?.pid&&q(_,"SIGTERM"),this.claudePty){try{this.claudePty.kill()}catch{}this.claudePty=null}throw this.claudeProcess===_&&(this.claudeProcess=null),this.claudeChildPid=0,m&&this.releaseNotifyPortReservation(),this.stopMcpServer(),P}const F=this.claudeChildPid;o.info("claude-adapter","Claude child PID: "+F+(this.claudePty?" (via PTY)":"")),this.setupProcessOrPtyHandlers(),this.alive=!0}finalizeActiveEventOnProcessExit(e){if(!this.activeEvent||this.stopped)return;const t=this.isEventAnswered(),i=t?"responded":"failed",s=!t&&this.activeEvent.apiFormatError?de(this.activeEvent.apiErrorText):void 0,n=t?void 0:s??`Claude process exited before completing its reply (${e})`;o.error("claude-adapter",`Claude process exited with active event ${this.activeEvent.eventId}, answered=${t}, sending ${i}`),s&&(this.bridgeCallbacks.sendStreamChunk(this.activeEvent.eventId,this.activeEvent.sessionId,s,1,!1,`err_${this.activeEvent.eventId}`),this.bridgeCallbacks.sendStreamChunk(this.activeEvent.eventId,this.activeEvent.sessionId,"",2,!0,`err_${this.activeEvent.eventId}`)),s?this.bridgeCallbacks.sendEventResult(this.activeEvent.eventId,i,n,void 0,!0):this.bridgeCallbacks.sendEventResult(this.activeEvent.eventId,i,n),this.clearActiveEvent()}setupProcessOrPtyHandlers(){if(this.claudeProcess){const e=this.claudeProcess;e.on("exit",(t,i)=>{if(o.info("claude-adapter",`Claude process exited (code=${t}, signal=${i})`),this.singleTurnFallbackProcesses.delete(e)){o.info("claude-adapter","Ignoring stale Claude process exit after lifecycle ownership transfer");return}if(this.lostInjectionKilledProcesses.delete(e)){o.info("claude-adapter","Ignoring expected Claude process exit after lost-injection respawn");return}this.claudeProcess=null,this.claudeChildPid=0,this.alive=!1,this.releaseNotifyPortReservation(),this.stopMcpServer(),this.clearPendingMcpFailureTimer(),!this.tryRecoverSessionIdConflict()&&(this.activeEvent&&!this.stopped&&this.finalizeActiveEventOnProcessExit(`code=${t}, signal=${i}`),this.stopped||this.emit("exit",t),this.compacting&&this.finishCompaction("process-exit"))}),e.stdout?.on("data",t=>{this.claudeProcess===e&&this.handleClaudeOutput(t.toString())}),e.stderr?.on("data",t=>{if(this.claudeProcess!==e)return;const i=t.toString().trim();i&&o.info("claude-adapter",`[claude stderr] ${i}`),this.checkFailurePatterns(i,"stderr")})}if(this.claudePty){const e=this.claudePty;e.onExit(({exitCode:t,signal:i})=>{if(o.info("claude-adapter",`Claude PTY process exited (code=${t}, signal=${i})`),this.singleTurnFallbackPtys.delete(e)){o.info("claude-adapter","Ignoring stale Claude PTY exit after lifecycle ownership transfer");return}if(this.lostInjectionKilledPtys.delete(e)){o.info("claude-adapter","Ignoring expected Claude PTY exit after lost-injection respawn");return}this.claudePty=null,this.claudeChildPid=0,this.alive=!1,this.releaseNotifyPortReservation(),this.stopMcpServer(),this.clearPendingMcpFailureTimer(),!this.tryRecoverSessionIdConflict()&&(this.activeEvent&&!this.stopped&&this.finalizeActiveEventOnProcessExit(`code=${t}, signal=${i}`),this.stopped||this.emit("exit",t),this.compacting&&this.finishCompaction("process-exit"))}),e.onData(t=>{this.claudePty===e&&(this.handleClaudeOutput(t),this.checkFailurePatterns(t,"pty"))})}}lastPtyOutputAt=0;ptyProbe={title:0,spinner:0,meaningful:0,lastLogAt:0,lastMeaningfulAt:0};recordPtyProbe(e){const t=Date.now();this.lastPtyOutputAt=t;const i=e.includes("]0;"),s=e.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g,"").replace(/[\x00-\x1f\x7f-\x9f]/g,"").trim(),n=s.length<=3||/^[✳✶✻✽✵❋✺✴❈❖✦✧✢◉◎⬥⬦◇◆▸▹►▻→←↑↓⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏·…\d]*still\s+thinking/i.test(s);if(i?this.ptyProbe.title++:!s||n?this.ptyProbe.spinner++:(this.ptyProbe.meaningful++,this.ptyProbe.lastMeaningfulAt=t),t-this.ptyProbe.lastLogAt>=2e3){const r=this.activeEvent?this.isEventAnswered()?"replied":"running":"none",a=this.ptyProbe.lastMeaningfulAt?t-this.ptyProbe.lastMeaningfulAt:-1;o.debug("claude-adapter",`[pty-probe] session=${this.sessionId??"?"} event=${r} win2s title=${this.ptyProbe.title} spinner=${this.ptyProbe.spinner} meaningful=${this.ptyProbe.meaningful} meaningfulGapMs=${a}`),this.ptyProbe.title=0,this.ptyProbe.spinner=0,this.ptyProbe.meaningful=0,this.ptyProbe.lastLogAt=t}}terminalPermissionPromptTail="";terminalPermissionPromptVisible=!1;terminalPermissionKeyTimer=null;trackTerminalPermissionPrompt(e){const t=e.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g," ").replace(/[\x00-\x1f\x7f-\x9f]/g," ").trim();if(t){if(wi.test(t)){this.terminalPermissionPromptTail=`${this.terminalPermissionPromptTail} ${t}`.slice(-Si),yi.test(this.terminalPermissionPromptTail)&&(this.terminalPermissionPromptVisible=!0);return}t.length<=3||Pi.test(t)||(this.terminalPermissionPromptTail="",this.terminalPermissionPromptVisible=!1)}}sendTerminalKey(e){const t=e==="enter"?"\r":"\x1B";try{if(this.claudePty)return this.claudePty.write(t),!0;if(this.expectRunDir)return this.writeExpectFifoLine(this.expectRunDir,`${fe}${e}`),!0}catch(i){o.warn("claude-adapter",`Failed to send terminal key ${e}: ${i instanceof Error?i.message:i}`)}return!1}scheduleTerminalPermissionKey(e,t){this.terminalPermissionKeyTimer&&clearTimeout(this.terminalPermissionKeyTimer),this.terminalPermissionKeyTimer=setTimeout(()=>{if(this.terminalPermissionKeyTimer=null,this.stopped||!this.terminalPermissionPromptVisible)return;const i=t==="allow"?"enter":"esc";this.sendTerminalKey(i)&&(this.terminalPermissionPromptTail="",this.terminalPermissionPromptVisible=!1,o.info("claude-adapter",`Permission prompt still on terminal after ${t}, sent ${i} key: approvalId=${e}`))},_i),this.terminalPermissionKeyTimer.unref()}handleClaudeOutput(e){this.recordPtyProbe(e),this.trackTerminalPermissionPrompt(e),this.compacting&&this.resetCompactingActivityTimer();const t=e.trim();if(t){const a=t.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g,"").replace(/[\x00-\x1f\x7f-\x9f\u200b-\u200f\u2028-\u202f\ufeff]/g,"").trim();a?a.length<=3||/^[✳✶✻✽✵❋✺✴❈❖✦✧✢◉◎⬥⬦◇◆▸▹►▻→←↑↓⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏·…\d]*still\s+thinking/i.test(a)?o.debug("claude-adapter",`[claude output] ${a}`):o.info("claude-adapter",`[claude output] ${a.slice(0,500)}`):o.debug("claude-adapter",`[claude output] ${t.slice(0,200).replace(/[^\x20-\x7e]/g,l=>`\\x${l.charCodeAt(0).toString(16).padStart(2,"0")}`)}`)}this.activeEvent&&this.markActiveEventActivity(this.activeEvent.eventId,this.activeEvent.sessionId);const i=t.match(/Session ID (\S+) is already in use/i);if(i){const a=i[1];this.sessionIdConflictDetected=!0;const l=[this.claudeProcess?.pid,this.claudeChildPid,this.claudePty?.pid].filter(c=>!!c&&c>0);me(a,l)}const s=t.replace(/\[[0-9;?]*[ -/]*[@-~]/g," ").replace(/[^a-zA-Z]+/g," ").toLowerCase(),n=s.replace(/ /g,"");if(this.claudePty&&!this.startupChannelListening&&!this.windowsTrustDialogAnswered&&/trust.*folder|quick.*safety.*check/.test(n)&&this.respondToWindowsTrustDialog(t),!this.startupDialogSeenAt&&!this.startupChannelListening&&/loadingdevelopmentchannels|usingthisforlocaldevelopment/.test(n)&&(this.startupDialogSeenAt=Date.now(),o.info("claude-adapter","Dev channels dialog rendered; restarting notify port wait window"),this.claudePty&&this.scheduleDevChannelsConfirm()),(/listeningforch\w*nelmessages/.test(n)||/nnelmessagesfrom/.test(n)||/inboundmessageswillbepushedintothissession/.test(n))&&(this.startupChannelListening||(this.startupChannelListeningAt=Date.now(),this.startupChannelListening=!0,this.sendDeferredModelSwitch()),this.clearPendingMcpFailureTimer()),!this.startupChannelListening&&/channels?arenotcurrentlyavailable/.test(n)&&(this.channelGateClosed=!0,o.warn("claude-adapter",'Claude reports "Channels are not currently available" \u2014 channel gate (tengu_harbor) is closed for this account/model; switching to single-turn fallback')),!this.mcpStartupFailureHandled&&s.includes("mcp server failed")){if(this.startupChannelListening){o.warn("claude-adapter","Claude reported MCP server failed for one channel, but channel listening is active; ignoring non-blocking failure");return}this.pendingMcpFailureTimer||(this.pendingMcpFailureTimer=setTimeout(()=>{this.pendingMcpFailureTimer=null,!(this.startupChannelListening||this.mcpStartupFailureHandled)&&this.markMcpStartupFailure()},pi))}}clearWindowsTrustDialogTimer(){this.windowsTrustDialogTimer&&(clearTimeout(this.windowsTrustDialogTimer),this.windowsTrustDialogTimer=null)}respondToWindowsTrustDialog(e){if(this.windowsTrustDialogAnswered||this.windowsTrustDialogTimer)return;const t=e.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g,"").replace(/[\x00-\x1f\x7f-\x9f]/g,""),i=$i.test(t),s=!i&&Oi.test(t);!i&&!s||(this.windowsTrustDialogAnswered=!0,o.info("claude-adapter",`Auto-accepting workspace trust dialog (layout=${i?"noFirst":"yesFirst"})`),this.scheduleTrustDialogWrite(i,1e3))}scheduleTrustDialogWrite(e,t){this.windowsTrustDialogTimer=setTimeout(()=>{if(this.windowsTrustDialogTimer=null,this.stopped||!this.claudePty)return;if(this.ptyDialogBusy){this.scheduleTrustDialogWrite(e,Ze);return}this.ptyDialogBusy=!0;const i=e?"noFirst":"yesFirst";e?(this.writePtyKey(this.claudePty,"\x1B[B","Down","trust",i),this.windowsTrustDialogTimer=setTimeout(()=>{this.windowsTrustDialogTimer=null,this.claudePty&&!this.stopped&&this.writePtyKey(this.claudePty,"\r","Enter","trust",i),this.ptyDialogBusy=!1},400),this.windowsTrustDialogTimer.unref()):(this.writePtyKey(this.claudePty,"\r","Enter","trust",i),this.ptyDialogBusy=!1)},t),this.windowsTrustDialogTimer.unref()}writePtyKey(e,t,i,s,n){try{e.write(t),o.info("claude-adapter",`PTY key sent: ${i} (dialog=${s}${n?`, layout=${n}`:""})`)}catch(r){o.warn("claude-adapter",`Failed to write PTY key ${i} for dialog=${s}: ${r instanceof Error?r.message:r}`)}}scheduleDevChannelsConfirm(){this.devChannelsConfirmTimer||(this.devChannelsConfirmTimer=setTimeout(()=>this.tryDevChannelsConfirm(),vi),this.devChannelsConfirmTimer.unref())}tryDevChannelsConfirm(){if(this.devChannelsConfirmTimer=null,this.stopped||!this.claudePty||this.startupChannelListening)return;if(this.ptyDialogBusy){this.devChannelsConfirmTimer=setTimeout(()=>this.tryDevChannelsConfirm(),Ze),this.devChannelsConfirmTimer.unref();return}this.ptyDialogBusy=!0,this.writePtyKey(this.claudePty,"\r","Enter","devChannels"),this.ptyDialogBusy=!1,Date.now()-(this.startupDialogSeenAt||Date.now())<Ei&&(this.devChannelsConfirmTimer=setTimeout(()=>this.tryDevChannelsConfirm(),gi),this.devChannelsConfirmTimer.unref())}clearDevChannelsConfirmTimer(){this.devChannelsConfirmTimer&&(clearTimeout(this.devChannelsConfirmTimer),this.devChannelsConfirmTimer=null)}checkFailurePatterns(e,t="stderr"){/Session ID (\S+) is already in use/i.test(e)&&(this.sessionIdConflictDetected=!0);const s=(t==="pty"?Wt:Gt).some(n=>n.test(e));s&&this.isEventAnswered()?o.warn("claude-adapter",`Ignoring ${t} auth failure text after current event was answered`):s&&(o.error("claude-adapter",`Auth failure: ${e}`),this.authFailureUntil=Date.now()+Le,this.activeEvent&&(this.bridgeCallbacks.sendStreamChunk(this.activeEvent.eventId,this.activeEvent.sessionId,`
8
+ `)}}),E.stderr?.setEncoding("utf8"),E.stderr?.on("data",R=>{A.length>=he||(A+=R,A.length>he&&(A=A.slice(0,he)))}),E.on("error",R=>{b(),g(R)}),E.on("close",R=>{if(b(),$.trim()&&Se($),ie){g(ie);return}if(R!==0&&!U){const C=A.trim();g(new Error(C||`claude single-turn exited with code ${R}`));return}if(!U&&this.activeEvent?.eventId===e.event_id&&!this.isEventAnswered()){const C=A.trim();g(new Error(C||"claude single-turn produced no output"));return}U&&(j+=1,this.bridgeCallbacks.sendStreamChunk(e.event_id,e.session_id,"",j,!0,Ee,_e)),x()})}),this.activeEvent?.eventId===e.event_id&&(this.bridgeCallbacks.sendEventResult(e.event_id,"responded"),this.clearActiveEvent())}async waitForChannelReady(){if(!this.mcpServerReady)throw new Error("MCP stdio server not available");await this.waitForNotifyPort(3e4),process.platform==="win32"?await this.waitForWindowsChannelReady(Xe):await this.waitForChannelListening(Xe)}async ensureClaudeProcessReady(){this.claudeProcess||this.claudePty||(this.spawnPromise||(this.spawnPromise=this.spawnClaude().finally(()=>{this.spawnPromise=null})),await this.spawnPromise)}async spawnClaude(){if(this.claudeProcess)return;const e=this.lifecycleVersion,t=()=>{if(this.stopped||e!==this.lifecycleVersion)throw new Error("adapter stopped")},i=this.config.options??{};this.sessionIdConflictDetected=!1;const s=this.resolveSessionRuntime(),n=this.config.command||"claude",r=this.config.args??[],a=tt(s.modeId),l=String(s.modelId??"").trim(),c=String(s.cwd??"").trim();this.claudeSessionCwd=c;const u=String(s.claudeSessionId??"").trim()||J();this.claudeCliSessionId=u,!s.claudeSessionId&&s.onSessionIdAssigned&&s.onSessionIdAssigned(u);const h=this.buildClaudeRuntimeEnv((()=>{const P=this.resolveEventAuditConfig(s,this.activeEvent?.rawEvent);return P?{...s,audit:P}:s})());let p=at(u,c||void 0);if(p&&ot(u,c||void 0)&&(p=!1),String(s.claudeSessionId??"").trim()!==""&&!p&&o.warn("claude-adapter",`Claude local transcript missing for session ${u}; continuing silently with platform context`),p?lt(u,c||void 0):(me(u,[]),ve(u)),this.runtimeResolver&&!c)throw new Error("Claude session binding missing cwd \u2014 run /grix open <working-directory> first");if(!c)throw new Error("Claude runtime cwd is required");await this.ensureWorkspaceTrust(c),await this.ensureClaudeOnboardingFlags(c),await this.ensureSkipDangerousPermissionPrompt(),await this.injectStatusLineSettings(c);let m=!1,v="",_=null;try{t(),this.notifyPort=await Ci([this.internalApiPort]),m=!0,t(),o.info("claude-adapter",`Allocated MCP notify port ${this.notifyPort} (internal API ${this.internalApiPort})`),await this.ensureStdioMcpServer(),t();const P=i.pluginDir||await this.ensureClaudePluginDir(),x=p?["--resume",u]:["--session-id",u],g=[...r,"--name",`grix-${this.sessionId}`,...x,"--plugin-dir",P,"--strict-mcp-config","--mcp-config",this.claudeMcpConfigPath];l&&(/^claude/i.test(l)?g.push("--model",l):(o.warn("claude-adapter",`Skipping --model ${l}: non-Anthropic models are incompatible with development channels. Using default model so channels can load. Switch models after startup via /model if needed.`),this.deferredModelId=l)),a===G.fullAuto&&g.push("--dangerously-skip-permissions"),g.push("--dangerously-load-development-channels",`server:${M}`);const N=Ii(this.bridgeCallbacks.getAgentProfile?.(),p?void 0:this.sessionId,this.bridgeCallbacks.getAgentId?.());if(process.platform==="win32"){const E=N.replace(/</g,"[").replace(/>/g,"]").replace(/"/g,"'"),b=Ni(this.claudeCliSessionId);await Hi(b,E),g.push("--append-system-prompt-file",b)}else g.push("--append-system-prompt",N);if(process.platform==="win32")if(Y){const E=qi(),b=["/c",n,...g];o.info("claude-adapter",`Spawning Claude via PTY on win32: cwd=${c} mode=${a} ${p?"resume":"new"} ${n} ${g.join(" ")}`);const A=Y.spawn(E,b,{name:"xterm-256color",cols:120,rows:30,cwd:c,env:h,useConpty:!0,conptyInheritCursor:!1});this.claudePty=A,this.claudeChildPid=A.pid,_=null}else o.info("claude-adapter",`Spawning Claude via shell on win32 (node-pty unavailable): cwd=${c} mode=${a} ${p?"resume":"new"} ${n} ${g.join(" ")}`),_=se(n,g,{cwd:c,env:h,stdio:["pipe","pipe","pipe"],detached:process.platform!=="win32",shell:!0,windowsHide:!0});else{o.info("claude-adapter",`Spawning via expect PTY: cwd=${c} mode=${a} ${p?"resume":"new"} ${n} ${g.join(" ")}`);const E=Zt(typeof h.PATH=="string"?h.PATH:void 0);if(!E)throw new Error("expect not found in PATH. Install it with: apt install expect / dnf install expect / brew install expect");const b=f(Pt(),`grix-claude-${J()}`);await L(b,{recursive:!0}),this.expectRunDir=b;const{expectPath:A,pidPath:j}=await xi(b,n,g);v=j,t(),_=se(E,[A],{cwd:c,env:h,stdio:["pipe","pipe","pipe"],detached:!0})}if(this.claudeProcess=_,this.clearPendingMcpFailureTimer(),this.mcpStartupFailureHandled=!1,this.mcpChannelBroken=!1,this.channelGateClosed=!1,this.startupChannelListening=!1,this.startupChannelListeningAt=0,this.startupDialogSeenAt=0,this.clearWindowsTrustDialogTimer(),this.windowsTrustDialogAnswered=!1,this.clearDevChannelsConfirmTimer(),this.ptyDialogBusy=!1,_&&_.on("error",E=>{o.error("claude-adapter","Claude process spawn error: "+E),this.claudeProcess=null,this.claudeChildPid=0,this.alive=!1,this.releaseNotifyPortReservation(),this.stopMcpServer(),this.clearPendingMcpFailureTimer(),this.activeEvent&&(this.bridgeCallbacks.sendEventResult(this.activeEvent.eventId,"failed","Claude process spawn failed"),this.clearActiveEvent())}),t(),!this.claudePty)if(process.platform==="win32")this.claudeChildPid=_?.pid??0;else{const E=await Mi(_,v);if(t(),!Number.isFinite(E)||E<=0)throw new Error("failed to determine spawned Claude pid");this.claudeChildPid=E}if(!this.claudeChildPid||this.claudeChildPid<=0)throw new Error("failed to determine spawned Claude pid")}catch(P){if(_?.pid&&q(_,"SIGTERM"),this.claudePty){try{this.claudePty.kill()}catch{}this.claudePty=null}throw this.claudeProcess===_&&(this.claudeProcess=null),this.claudeChildPid=0,m&&this.releaseNotifyPortReservation(),this.stopMcpServer(),P}const F=this.claudeChildPid;o.info("claude-adapter","Claude child PID: "+F+(this.claudePty?" (via PTY)":"")),this.setupProcessOrPtyHandlers(),this.alive=!0}finalizeActiveEventOnProcessExit(e){if(!this.activeEvent||this.stopped)return;const t=this.isEventAnswered(),i=t?"responded":"failed",s=!t&&this.activeEvent.apiFormatError?de(this.activeEvent.apiErrorText):void 0,n=t?void 0:s??`Claude process exited before completing its reply (${e})`;o.error("claude-adapter",`Claude process exited with active event ${this.activeEvent.eventId}, answered=${t}, sending ${i}`),s&&(this.bridgeCallbacks.sendStreamChunk(this.activeEvent.eventId,this.activeEvent.sessionId,s,1,!1,`err_${this.activeEvent.eventId}`),this.bridgeCallbacks.sendStreamChunk(this.activeEvent.eventId,this.activeEvent.sessionId,"",2,!0,`err_${this.activeEvent.eventId}`)),s?this.bridgeCallbacks.sendEventResult(this.activeEvent.eventId,i,n,void 0,!0):this.bridgeCallbacks.sendEventResult(this.activeEvent.eventId,i,n),this.clearActiveEvent()}setupProcessOrPtyHandlers(){if(this.claudeProcess){const e=this.claudeProcess;e.on("exit",(t,i)=>{if(o.info("claude-adapter",`Claude process exited (code=${t}, signal=${i})`),this.singleTurnFallbackProcesses.delete(e)){o.info("claude-adapter","Ignoring stale Claude process exit after lifecycle ownership transfer");return}if(this.lostInjectionKilledProcesses.delete(e)){o.info("claude-adapter","Ignoring expected Claude process exit after lost-injection respawn");return}this.claudeProcess=null,this.claudeChildPid=0,this.alive=!1,this.releaseNotifyPortReservation(),this.stopMcpServer(),this.clearPendingMcpFailureTimer(),!this.tryRecoverSessionIdConflict()&&(this.activeEvent&&!this.stopped&&this.finalizeActiveEventOnProcessExit(`code=${t}, signal=${i}`),this.stopped||this.emit("exit",t),this.compacting&&this.finishCompaction("process-exit"))}),e.stdout?.on("data",t=>{this.claudeProcess===e&&this.handleClaudeOutput(t.toString())}),e.stderr?.on("data",t=>{if(this.claudeProcess!==e)return;const i=t.toString().trim();i&&o.info("claude-adapter",`[claude stderr] ${i}`),this.checkFailurePatterns(i,"stderr")})}if(this.claudePty){const e=this.claudePty;e.onExit(({exitCode:t,signal:i})=>{if(o.info("claude-adapter",`Claude PTY process exited (code=${t}, signal=${i})`),this.singleTurnFallbackPtys.delete(e)){o.info("claude-adapter","Ignoring stale Claude PTY exit after lifecycle ownership transfer");return}if(this.lostInjectionKilledPtys.delete(e)){o.info("claude-adapter","Ignoring expected Claude PTY exit after lost-injection respawn");return}this.claudePty=null,this.claudeChildPid=0,this.alive=!1,this.releaseNotifyPortReservation(),this.stopMcpServer(),this.clearPendingMcpFailureTimer(),!this.tryRecoverSessionIdConflict()&&(this.activeEvent&&!this.stopped&&this.finalizeActiveEventOnProcessExit(`code=${t}, signal=${i}`),this.stopped||this.emit("exit",t),this.compacting&&this.finishCompaction("process-exit"))}),e.onData(t=>{this.claudePty===e&&(this.handleClaudeOutput(t),this.checkFailurePatterns(t,"pty"))})}}lastPtyOutputAt=0;ptyProbe={title:0,spinner:0,meaningful:0,lastLogAt:0,lastMeaningfulAt:0};recordPtyProbe(e){const t=Date.now();this.lastPtyOutputAt=t;const i=e.includes("]0;"),s=e.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g,"").replace(/[\x00-\x1f\x7f-\x9f]/g,"").trim(),n=s.length<=3||/^[✳✶✻✽✵❋✺✴❈❖✦✧✢◉◎⬥⬦◇◆▸▹►▻→←↑↓⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏·…\d]*still\s+thinking/i.test(s);if(i?this.ptyProbe.title++:!s||n?this.ptyProbe.spinner++:(this.ptyProbe.meaningful++,this.ptyProbe.lastMeaningfulAt=t),t-this.ptyProbe.lastLogAt>=2e3){const r=this.activeEvent?this.isEventAnswered()?"replied":"running":"none",a=this.ptyProbe.lastMeaningfulAt?t-this.ptyProbe.lastMeaningfulAt:-1;o.debug("claude-adapter",`[pty-probe] session=${this.sessionId??"?"} event=${r} win2s title=${this.ptyProbe.title} spinner=${this.ptyProbe.spinner} meaningful=${this.ptyProbe.meaningful} meaningfulGapMs=${a}`),this.ptyProbe.title=0,this.ptyProbe.spinner=0,this.ptyProbe.meaningful=0,this.ptyProbe.lastLogAt=t}}terminalPermissionPromptTail="";terminalPermissionPromptVisible=!1;terminalPermissionKeyTimer=null;trackTerminalPermissionPrompt(e){const t=e.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g," ").replace(/[\x00-\x1f\x7f-\x9f]/g," ").trim();if(t){if(wi.test(t)){this.terminalPermissionPromptTail=`${this.terminalPermissionPromptTail} ${t}`.slice(-Si),yi.test(this.terminalPermissionPromptTail)&&(this.terminalPermissionPromptVisible=!0);return}t.length<=3||Pi.test(t)||(this.terminalPermissionPromptTail="",this.terminalPermissionPromptVisible=!1)}}sendTerminalKey(e){const t=e==="enter"?"\r":"\x1B";try{if(this.claudePty)return this.claudePty.write(t),!0;if(this.expectRunDir)return this.writeExpectFifoLine(this.expectRunDir,`${fe}${e}`),!0}catch(i){o.warn("claude-adapter",`Failed to send terminal key ${e}: ${i instanceof Error?i.message:i}`)}return!1}scheduleTerminalPermissionKey(e,t){this.terminalPermissionKeyTimer&&clearTimeout(this.terminalPermissionKeyTimer),this.terminalPermissionKeyTimer=setTimeout(()=>{if(this.terminalPermissionKeyTimer=null,this.stopped||!this.terminalPermissionPromptVisible)return;const i=t==="allow"?"enter":"esc";this.sendTerminalKey(i)&&(this.terminalPermissionPromptTail="",this.terminalPermissionPromptVisible=!1,o.info("claude-adapter",`Permission prompt still on terminal after ${t}, sent ${i} key: approvalId=${e}`))},_i),this.terminalPermissionKeyTimer.unref()}handleClaudeOutput(e){this.recordPtyProbe(e),this.trackTerminalPermissionPrompt(e),this.compacting&&this.resetCompactingActivityTimer();const t=e.trim();if(t){const a=t.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g,"").replace(/[\x00-\x1f\x7f-\x9f\u200b-\u200f\u2028-\u202f\ufeff]/g,"").trim();a?a.length<=3||/^[✳✶✻✽✵❋✺✴❈❖✦✧✢◉◎⬥⬦◇◆▸▹►▻→←↑↓⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏·…\d]*still\s+thinking/i.test(a)?o.debug("claude-adapter",`[claude output] ${a}`):o.info("claude-adapter",`[claude output] ${a.slice(0,500)}`):o.debug("claude-adapter",`[claude output] ${t.slice(0,200).replace(/[^\x20-\x7e]/g,l=>`\\x${l.charCodeAt(0).toString(16).padStart(2,"0")}`)}`)}this.activeEvent&&this.markActiveEventActivity(this.activeEvent.eventId,this.activeEvent.sessionId);const i=t.match(/Session ID (\S+) is already in use/i);if(i){const a=i[1];this.sessionIdConflictDetected=!0;const l=[this.claudeProcess?.pid,this.claudeChildPid,this.claudePty?.pid].filter(c=>!!c&&c>0);me(a,l)}const s=t.replace(/\[[0-9;?]*[ -/]*[@-~]/g," ").replace(/[^a-zA-Z]+/g," ").toLowerCase(),n=s.replace(/ /g,"");if(this.claudePty&&!this.startupChannelListening&&!this.windowsTrustDialogAnswered&&/trust.*folder|quick.*safety.*check/.test(n)&&this.respondToWindowsTrustDialog(t),!this.startupDialogSeenAt&&!this.startupChannelListening&&/loadingdevelopmentchannels|usingthisforlocaldevelopment/.test(n)&&(this.startupDialogSeenAt=Date.now(),o.info("claude-adapter","Dev channels dialog rendered; restarting notify port wait window"),this.claudePty&&this.scheduleDevChannelsConfirm()),(/listeningforch\w*nelmessages/.test(n)||/nnelmessagesfrom/.test(n)||/inboundmessageswillbepushedintothissession/.test(n))&&(this.startupChannelListening||(this.startupChannelListeningAt=Date.now(),this.startupChannelListening=!0,this.sendDeferredModelSwitch()),this.clearPendingMcpFailureTimer()),!this.startupChannelListening&&/channels?arenotcurrentlyavailable/.test(n)&&(this.channelGateClosed=!0,o.warn("claude-adapter",'Claude reports "Channels are not currently available" \u2014 channel gate (tengu_harbor) is closed for this account/model; switching to single-turn fallback')),!this.mcpStartupFailureHandled&&s.includes("mcp server failed")){if(this.startupChannelListening){o.warn("claude-adapter","Claude reported MCP server failed for one channel, but channel listening is active; ignoring non-blocking failure");return}this.pendingMcpFailureTimer||(this.pendingMcpFailureTimer=setTimeout(()=>{this.pendingMcpFailureTimer=null,!(this.startupChannelListening||this.mcpStartupFailureHandled)&&this.markMcpStartupFailure()},pi))}}clearWindowsTrustDialogTimer(){this.windowsTrustDialogTimer&&(clearTimeout(this.windowsTrustDialogTimer),this.windowsTrustDialogTimer=null)}respondToWindowsTrustDialog(e){if(this.windowsTrustDialogAnswered||this.windowsTrustDialogTimer)return;const t=e.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g,"").replace(/[\x00-\x1f\x7f-\x9f]/g,""),i=$i.test(t),s=!i&&Oi.test(t);!i&&!s||(this.windowsTrustDialogAnswered=!0,o.info("claude-adapter",`Auto-accepting workspace trust dialog (layout=${i?"noFirst":"yesFirst"})`),this.scheduleTrustDialogWrite(i,1e3))}scheduleTrustDialogWrite(e,t){this.windowsTrustDialogTimer=setTimeout(()=>{if(this.windowsTrustDialogTimer=null,this.stopped||!this.claudePty)return;if(this.ptyDialogBusy){this.scheduleTrustDialogWrite(e,Ze);return}this.ptyDialogBusy=!0;const i=e?"noFirst":"yesFirst";e?(this.writePtyKey(this.claudePty,"\x1B[B","Down","trust",i),this.windowsTrustDialogTimer=setTimeout(()=>{this.windowsTrustDialogTimer=null,this.claudePty&&!this.stopped&&this.writePtyKey(this.claudePty,"\r","Enter","trust",i),this.ptyDialogBusy=!1},400),this.windowsTrustDialogTimer.unref()):(this.writePtyKey(this.claudePty,"\r","Enter","trust",i),this.ptyDialogBusy=!1)},t),this.windowsTrustDialogTimer.unref()}writePtyKey(e,t,i,s,n){try{e.write(t),o.info("claude-adapter",`PTY key sent: ${i} (dialog=${s}${n?`, layout=${n}`:""})`)}catch(r){o.warn("claude-adapter",`Failed to write PTY key ${i} for dialog=${s}: ${r instanceof Error?r.message:r}`)}}scheduleDevChannelsConfirm(){this.devChannelsConfirmTimer||(this.devChannelsConfirmTimer=setTimeout(()=>this.tryDevChannelsConfirm(),vi),this.devChannelsConfirmTimer.unref())}tryDevChannelsConfirm(){if(this.devChannelsConfirmTimer=null,this.stopped||!this.claudePty||this.startupChannelListening)return;if(this.ptyDialogBusy){this.devChannelsConfirmTimer=setTimeout(()=>this.tryDevChannelsConfirm(),Ze),this.devChannelsConfirmTimer.unref();return}this.ptyDialogBusy=!0,this.writePtyKey(this.claudePty,"\r","Enter","devChannels"),this.ptyDialogBusy=!1,Date.now()-(this.startupDialogSeenAt||Date.now())<Ei&&(this.devChannelsConfirmTimer=setTimeout(()=>this.tryDevChannelsConfirm(),gi),this.devChannelsConfirmTimer.unref())}clearDevChannelsConfirmTimer(){this.devChannelsConfirmTimer&&(clearTimeout(this.devChannelsConfirmTimer),this.devChannelsConfirmTimer=null)}checkFailurePatterns(e,t="stderr"){/Session ID (\S+) is already in use/i.test(e)&&(this.sessionIdConflictDetected=!0);const s=(t==="pty"?Wt:zt).some(n=>n.test(e));s&&this.isEventAnswered()?o.warn("claude-adapter",`Ignoring ${t} auth failure text after current event was answered`):s&&(o.error("claude-adapter",`Auth failure: ${e}`),this.authFailureUntil=Date.now()+Le,this.activeEvent&&(this.bridgeCallbacks.sendStreamChunk(this.activeEvent.eventId,this.activeEvent.sessionId,`
9
9
 
10
10
  Error: Claude authentication failed -- please re-login`,1,!1),this.bridgeCallbacks.sendEventResult(this.activeEvent.eventId,"failed","Claude authentication failed -- please re-login"),this.clearActiveEvent())),Jt.some(n=>n.test(e))&&(o.error("claude-adapter",`Usage limit: ${e}`),this.usageLimitUntil=Date.now()+Le,this.activeEvent&&(this.bridgeCallbacks.sendStreamChunk(this.activeEvent.eventId,this.activeEvent.sessionId,`
11
11
 
@@ -14,8 +14,8 @@ Error: Claude usage limit reached -- waiting for reset`,1,!1),this.bridgeCallbac
14
14
  `,p=f(t,"hooks"),m=f(p,"hooks.json");await L(p,{recursive:!0});let v="";try{v=await T(m,"utf8")}catch{}v!==h&&(await D(m,h,"utf8"),o.info("claude-adapter",`Wrote Claude hooks config: ${m}`));const _=f(t,"skills"),F=Mt(_);return F.length>0&&o.info("claude-adapter",`Synced connector skills to plugin: [${F.join(", ")}]`),t}async ensureStdioMcpServer(){const e=this.resolveProjectRoot(),t=this.resolveStdioServerPath(e);if(this.ensureStdioServerArtifact(e,t),!S(t))throw new Error(`MCP stdio server entry point not found: ${t}`);const i=this.getInternalApiUrl(),s=this.notifyPort,n=[t,"--handle-url",i,"--notify-port",String(s)],r=f(e,"dist","mcp","mcp-bridge-server.js"),a=this.internalApi?.mcpBridgeWsUrl,l=S(r)&&a?[r,"--ws-url",a]:void 0;l||o.warn("claude-adapter",`APP MCP bridge skipped (path=${S(r)} ws=${!!a})`);const c=Fi(this.claudeCliSessionId);await Ui(c,n,l),this.claudeMcpConfigPath=c,this.mcpServerReady=!0,this.startActivityTracking()}resolveProjectRoot(){const e=Ct(import.meta.url);return wt(e,"..","..","..","..")}resolveStdioServerPath(e=this.resolveProjectRoot()){return f(e,"dist","mcp","stdio","server.js")}ensureStdioServerArtifact(e,t){if(S(t)||et)return;et=!0;const i=f(e,"node_modules","typescript","bin","tsc"),s=f(e,"tsconfig.json");if(!S(i)||!S(s))return;o.warn("claude-adapter",`MCP stdio server artifact missing, attempting build: ${t}`);const n=ne(process.execPath,[i,"-p",s],{cwd:e,env:process.env,encoding:"utf8",timeout:6e4,windowsHide:!0});if(n.status!==0){const r=`${n.stderr??""}${n.stdout??""}`.trim();throw new Error(`MCP stdio server build failed: ${r||`exit ${n.status??"unknown"}`}`)}}getInternalApiUrl(){return this.internalApi?this.internalApi.url:process.env.GRIX_CONNECTOR_INTERNAL_API?process.env.GRIX_CONNECTOR_INTERNAL_API:`http://127.0.0.1:${this.internalApiPort}`}internalApiPort=0;notifyPort=0;notifySocket=null;async waitForNotifyPort(e){if(this.notifyPort<=0)return;const t=Date.now();let i=t+e,s=!1;const n=()=>{s||this.startupDialogSeenAt<=t||(s=!0,i=this.startupDialogSeenAt+e,o.info("claude-adapter",`Notify port wait extended: dialog appeared ${this.startupDialogSeenAt-t}ms after spawn, allowing ${e}ms from there`))};for(;n(),!(Date.now()>=i);)try{await new Promise((a,l)=>{const c=le.createConnection({host:"127.0.0.1",port:this.notifyPort},()=>{c.destroy(),a()});c.on("error",l),setTimeout(()=>{c.destroy(),l(new Error("probe timeout"))},2e3)});return}catch{await new Promise(a=>setTimeout(a,500))}const r=Date.now()-t;throw new Error(`Notify port ${this.notifyPort} not ready within ${r}ms${s?" (window restarted at dev channels dialog)":""}`)}async waitForChannelListening(e){const t=Date.now(),i=8e3;for(;Date.now()-t<e;){if(this.channelGateClosed)throw new Error("channels are not currently available (tengu_harbor gate closed)");if(this.startupChannelListening){const s=this.startupChannelListeningAt||Date.now(),n=Date.now()-s;if(n>=X)return;await new Promise(r=>setTimeout(r,X-n));return}if(this.alive&&this.notifyPort>0&&Date.now()-t>i){o.info("claude-adapter",`Channel listener fallback: notify port connected, assuming ready after ${Date.now()-t}ms (resume mode may skip "Listening" output)`),this.startupChannelListeningAt=Date.now(),this.startupChannelListening=!0,this.clearPendingMcpFailureTimer(),this.sendDeferredModelSwitch();return}await new Promise(s=>setTimeout(s,200))}throw new Error(`Claude channel listener not ready within ${e}ms`)}sendDeferredModelSwitch(){if(!this.deferredModelId)return;const e=this.deferredModelId;this.deferredModelId=null,setTimeout(()=>{const t=this.claudePty??this.claudeProcess?.stdin;if(!(!t||this.stopped))try{const i=`/model ${e}
15
15
  `;"write"in t?t.write(i):t.write(i,()=>{}),o.info("claude-adapter",`Deferred model switch: /model ${e}`)}catch{}},3e3)}async waitForWindowsChannelReady(e){const t=Date.now();let i=null;if(!this.claudePty){let s=!1;i=setInterval(()=>{if(this.startupChannelListening){i&&clearInterval(i);return}try{this.claudeProcess?.stdin?.writable&&(this.claudeProcess.stdin.write("\r"),s||(o.info("claude-adapter","Windows shell: sending Enter to auto-confirm dev channels dialog"),s=!0))}catch{}},3e3)}try{for(;Date.now()-t<e;){if(this.channelGateClosed)throw new Error("channels are not currently available (tengu_harbor gate closed)");if(this.startupChannelListening){const r=this.startupChannelListeningAt||Date.now(),a=Date.now()-r;if(a>=X)return;await new Promise(l=>setTimeout(l,X-a));return}const s=Date.now()-t,n=this.claudePty?mi:12e3;if(this.alive&&this.mcpServerReady&&s>n){o.info("claude-adapter",`Windows ${this.claudePty?"PTY":"shell"} fallback: assuming channel ready after ${s}ms (no stdout detection, MCP server connected)`),this.startupChannelListeningAt=Date.now(),this.startupChannelListening=!0,this.clearPendingMcpFailureTimer(),this.sendDeferredModelSwitch();return}await new Promise(r=>setTimeout(r,500))}}finally{i&&clearInterval(i)}throw new Error(`Windows channel ready check timed out within ${e}ms`)}pushNotification(e,t){if(!this.mcpServerReady||this.notifyPort<=0)return;const i=JSON.stringify({jsonrpc:"2.0",method:e,params:t});!this.notifySocket||this.notifySocket.destroyed?(this.notifySocket=le.createConnection({host:"127.0.0.1",port:this.notifyPort},()=>{this.notifySocket.write(i+`
16
16
  `)}),this.notifySocket.on("error",s=>{o.error("claude-adapter",`Notify socket error: ${s.message}`),this.notifySocket=null}),this.notifySocket.on("close",()=>{this.notifySocket=null})):this.notifySocket.write(i+`
17
- `)}stopMcpServer(){if(this.mcpServerReady=!1,this.notifySocket){try{this.notifySocket.destroy()}catch{}this.notifySocket=null}if(this.mcpServerProcess){try{this.mcpServerProcess.kill("SIGTERM")}catch{}this.mcpServerProcess=null}}releaseNotifyPortReservation(){Ti(this.notifyPort),this.notifyPort=0}killClaudeProcess(e){const t=this.claudeProcess,i=this.claudePty,s=this.claudeChildPid;if(e==="single-turn fallback isolation"&&(t&&this.singleTurnFallbackProcesses.add(t),i&&this.singleTurnFallbackPtys.add(i)),e===Be&&(t&&this.lostInjectionKilledProcesses.add(t),i&&this.lostInjectionKilledPtys.add(i)),this.claudeProcess=null,this.claudePty=null,this.claudeChildPid=0,this.spawnPromise=null,this.alive=!1,this.stopMcpServer(),this.releaseNotifyPortReservation(),this.mcpChannelBroken=!1,this.channelGateClosed=!1,this.startupChannelListening=!1,this.startupChannelListeningAt=0,this.deferredModelId=null,this.clearPendingMcpFailureTimer(),this.clearWindowsTrustDialogTimer(),this.windowsTrustDialogAnswered=!1,this.sessionIdConflictDetected=!1,this.pendingPermissions.size>0){const n=y(),r=new G(n.permissionRequestsDir);for(const[a]of this.pendingPermissions)r.resolveRequest(a,"deny").catch(()=>{});this.pendingPermissions.clear()}if(i)try{i.kill()}catch{}if(t?.pid&&q(t,"SIGTERM"),s>0)try{process.kill(s,"SIGTERM")}catch{}if(t?.pid||i||s>0){const n=s,r=t,a=i;setTimeout(()=>{if(a)try{a.kill()}catch{}if(r?.pid&&q(r,"SIGKILL"),n>0)try{process.kill(n,"SIGKILL")}catch{}},5e3).unref()}o.info("claude-adapter",`Claude process killed (reason=${e}, pid=${s}, expectPid=${t?.pid})`)}tryRecoverSessionIdConflict(){if(!this.sessionIdConflictDetected||this.stopped||!this.activeEvent||this.isEventAnswered())return!1;const e=this.activeEvent.eventId;if(this.sessionIdConflictRetriedEventIds.has(e))return this.sessionIdConflictDetected=!1,!1;this.sessionIdConflictRetriedEventIds.add(e),this.sessionIdConflictDetected=!1;const t=this.activeEvent.rawEvent;return o.warn("claude-adapter",`Detected Claude session-id conflict, auto-retrying event once: ${e}`),this.clearActiveEventIdleTimer(),this.clearActiveEventHardTimer(),this.activeEvent=null,this.stopComposing(),this.deliverInboundEvent(t),!0}recoverMalformedToolCall(e){if(this.stopped||!this.activeEvent||this.isEventAnswered()||!this.claudeCliSessionId||!this.claudeSessionCwd)return"none";const t=W(this.claudeCliSessionId,this.claudeSessionCwd,this.activeEvent.jsonlBaseOffset);if(!t||t.stopReason!=="stop_sequence"||t.text.trim()!==ti)return"none";if(this.malformedToolRetriedEventIds.has(e))return o.warn("claude-adapter",`Malformed tool-call retry exhausted for ${e}, sending clean fallback`),"exhausted";this.malformedToolRetriedEventIds.add(e);const i={...this.activeEvent.rawEvent,content:ii};return o.warn("claude-adapter",`Detected malformed tool-call, silently retrying event once: ${e}`),this.clearActiveEventIdleTimer(),this.clearActiveEventHardTimer(),this.activeEvent=null,this.stopComposing(),this.deliverInboundEvent(i),"retried"}async handleInternalInvoke(e,t,i,s){if(e==="mcp_stdio_log")return o.info("claude-mcp-stdio",String(t.message??"")),{ok:!0};if(e==="event_tool_call"){const n=String(t.tool_name??""),r=xt(n,{...t.arguments??{}}),a=this.getEventToolHandle();if(this.activeEvent){const c=this.activeEvent,u=String(r.event_id??"").trim();u===""?r.event_id=c.eventId:(n==="grix_reply"||n==="grix_complete")&&u!==c.eventId&&(o.warn("claude-adapter",`${n}: event_id mismatch, correcting: supplied=${u}, active=${c.eventId}`),r.event_id=c.eventId);const h=String(r.session_id??"").trim();if(n==="grix_reply"?(h&&h!==c.sessionId&&o.warn("claude-adapter",`${n}: session_id mismatch, correcting: supplied=${h}, active=${c.sessionId}`),r.session_id=c.sessionId):h===""&&(r.session_id=c.sessionId),n==="grix_reply"&&String(r.quoted_message_id??"").trim()===""){const p=String(c.rawEvent?.msg_id??"").trim();p&&(r.quoted_message_id=p)}n==="grix_reply"&&o.info("claude-adapter",`grix_reply quote: rawEvent.msg_id=${String(c.rawEvent?.msg_id??"")||"(none)"} -> quoted_message_id=${String(r.quoted_message_id??"")||"(none)"}`)}else if(this.lastClearedEvent&&n==="grix_reply"){const c=this.lastClearedEvent;Date.now()-c.ts<ui&&(String(r.session_id??"").trim()===""&&(r.session_id=c.sessionId),r.event_id="",o.info("claude-adapter",`Late grix_reply fallback: sending direct message for cleared event ${c.eventId} (cleared ${(Date.now()-c.ts)/1e3}s ago)`))}!this.activeEvent&&n==="grix_reply"&&(this.selfDrivenReplied=!0),n==="grix_reply"&&String(r.event_id??"").trim()!==""&&this.completedEventIds.has(String(r.event_id??"").trim())&&(o.info("claude-adapter",`Late grix_reply fallback: sending direct message for completed event ${String(r.event_id??"").trim()}`),r.event_id="");const l=$t(n,r);if(!l.valid)throw new Error(`\u53C2\u6570\u6821\u9A8C\u5931\u8D25: ${l.error}`);if(a.status!=="ready")throw new Error(`\u8FDE\u63A5\u4E0D\u53EF\u7528: \u5F53\u524D\u72B6\u6001\u4E3A ${a.status}`);if(Dt(n)){const c=this.activeEvent;c&&(c.toolCallInFlight=!0);try{if(i?.aborted)throw new Error("invoke aborted by timeout");const u=Re(a,n,r);if(u.isError)throw new Error(u.content[0]?.text??"event tool failed");if(i?.aborted)throw new Error("invoke aborted by timeout after send");return this.postProcessEventToolCall(n,r),JSON.parse(u.content[0]?.text??"null")}finally{if(c&&(c.toolCallInFlight=!1,c.pendingStopHook&&this.activeEvent===c)){const u=c.pendingStopHook;c.pendingStopHook=void 0,this.finalizeActiveEvent(u)}}}throw new Error(`\u672A\u77E5\u4E8B\u4EF6\u5DE5\u5177: ${n}`)}return this.bridgeCallbacks.agentInvoke(e,t,s)}getEventToolHandle(){const e=this;return{status:"ready",getStatusSnapshot:()=>({status:"ready",connectedAt:Date.now(),reconnectAttempts:0}),sendEventAck:t=>{e.bridgeCallbacks.sendEventAck(t.event_id,t.session_id??"")},sendStreamChunk:t=>{e.bridgeCallbacks.sendStreamChunk(t.event_id??"",t.session_id,t.delta_content??"",Number(t.chunk_seq??0)||1,t.is_finish===!0,t.client_msg_id,t.quoted_message_id)},sendMsg:t=>{if(typeof e.bridgeCallbacks.sendDirectMessage!="function"){o.warn("claude-adapter","sendDirectMessage callback not provided, dropping direct message");return}e.bridgeCallbacks.sendDirectMessage({sessionId:t.session_id,clientMsgId:t.client_msg_id,content:t.content,quotedMessageId:t.quoted_message_id})},sendEventResult:t=>{e.bridgeCallbacks.sendEventResult(t.event_id,t.status,t.msg,t.code)},sendSessionActivitySet:t=>{}}}postProcessEventToolCall(e,t){const i=String(t.event_id??"").trim();if(!i||this.activeEvent?.eventId!==i){o.warn("claude-adapter",`postProcessEventToolCall: event_id mismatch (tool=${e}, eventId=${i}, activeEventId=${this.activeEvent?.eventId??"none"})`);return}if(e==="grix_complete"){this.completedEventIds.set(i,Date.now()),this.clearActiveEvent();return}if(e==="grix_reply"){const s=!this.activeEvent.replied;this.activeEvent.replied=!0;const n=String(t.text??"");n.length>0&&(this.activeEvent.lastReplyText=n),this.markActiveEventActivity(i,String(t.session_id??"").trim()||void 0),s&&(this.activeEvent.repliedAt=Date.now(),this.startPostReplyDeadline(i),this.startPostReplyJsonlWatcher(i))}}resolveSessionRuntime(){if(!this.runtimeResolver)return{};try{return this.runtimeResolver(this.sessionId)??{}}catch(e){throw new Error(`resolve session runtime failed: ${e instanceof Error?e.message:String(e)}`)}}async validatePluginDir(e){const t=String(e??"").trim();if(!t)return;let i;try{i=await Ie(t)}catch{throw new Error(`pluginDir is not accessible: ${t}`)}if(!i.isDirectory())throw new Error(`pluginDir is not a directory: ${t}`);const s=f(t,".mcp.json");try{(await Ie(s)).isFile()&&(await Ae(s),o.info("claude-adapter",`Removed conflicting .mcp.json from pluginDir: ${s}`))}catch{}}async ensureWorkspaceTrust(e){const t=f(k(),".claude.json");let i=e;try{i=we(e)}catch{}const s=i===e?[e]:[e,i];try{const n=await T(t,"utf8"),r=JSON.parse(n.replace(/^\uFEFF/,""));r.projects||(r.projects={});let a=!1;for(const l of s)r.projects[l]?.hasTrustDialogAccepted!==!0&&(r.projects[l]||(r.projects[l]={}),r.projects[l].hasTrustDialogAccepted=!0,a=!0);if(!a)return;await D(t,JSON.stringify(r),"utf8"),o.info("claude-adapter",`Pre-trusted workspace: ${s.join(", ")}`)}catch(n){o.warn("claude-adapter",`Failed to pre-trust workspace ${e}: ${n}`)}}async ensureSkipDangerousPermissionPrompt(){const e=f(k(),".claude","settings.json");try{let t={};try{t=JSON.parse(await T(e,"utf8"))}catch{}if(t.skipDangerousModePermissionPrompt===!0)return;t.skipDangerousModePermissionPrompt=!0,await L(f(k(),".claude"),{recursive:!0}),await D(e,JSON.stringify(t,null,2),"utf8"),o.info("claude-adapter","Set skipDangerousModePermissionPrompt=true in user settings to skip BypassPermissions dialog")}catch(t){o.warn("claude-adapter",`Failed to set skipDangerousModePermissionPrompt: ${t}`)}}async isChannelGateClosed(){if(this.channelGateClosed)return!0;try{const e=await T(f(k(),".claude.json"),"utf8"),i=JSON.parse(e).cachedGrowthBookFeatures;return!i||Object.keys(i).length===0?!1:i.tengu_harbor!==!0}catch{return!1}}async ensureClaudeOnboardingFlags(e){const t=f(k(),".claude.json");try{let i;try{const r=await T(t,"utf8");i=JSON.parse(r)}catch{i={}}let s=!1;i.hasCompletedOnboarding||(i.hasCompletedOnboarding=!0,i.lastOnboardingVersion||(i.lastOnboardingVersion="2.1.31"),s=!0),i.projects||(i.projects={});const n=i.projects;if(n[e]||(n[e]={}),n[e].hasCompletedProjectOnboarding||(n[e].hasCompletedProjectOnboarding=!0,s=!0),this.config.env?.ANTHROPIC_API_KEY===Oe){const r=Oe.slice(-20),a=i.customApiKeyResponses??{},l=a.approved??[],c=a.rejected??[];(!l.includes(r)||c.includes(r))&&(i.customApiKeyResponses={...a,approved:[...l.filter(u=>u!==r),r],rejected:c.filter(u=>u!==r)},s=!0)}if(!s)return;await D(t,JSON.stringify(i,null,2),"utf8"),o.info("claude-adapter",`Marked Claude onboarding complete: ${e}`)}catch(i){o.warn("claude-adapter",`Failed to mark Claude onboarding complete: ${i}`)}}async injectStatusLineSettings(e){try{const t=this.resolveProjectRoot(),i=f(t,"dist","scripts","status-line-forwarder.js"),s=f(e,".claude"),n=f(s,"settings.json");await L(s,{recursive:!0});let r={};try{r=JSON.parse(await T(n,"utf8"))}catch{}const l={type:"command",command:`node "${i.replace(/\\/g,"/")}"`,refreshInterval:10};r.statusLine=l;const c=`${JSON.stringify(r,null,2)}
18
- `;await D(n,c,"utf8"),o.info("claude-adapter",`Injected statusLine settings: ${n}`)}catch(t){o.warn("claude-adapter",`Failed to inject statusLine settings: ${t instanceof Error?t.message:t}`)}}async ensureUserMcpServer(e,t,i){const s=this.resolveServerEntryPath(t),n=process.execPath,r=[s],a=f(k(),".claude.json");let l=null;try{const p=await T(a,"utf8");l=JSON.parse(p)?.mcpServers?.[M]??null}catch{}if(l&&String(l.type||"stdio").trim()==="stdio"&&String(l.command??"").trim()===n&&Array.isArray(l.args)&&l.args.length===r.length&&l.args.every((p,m)=>p===r[m]))return;o.info("claude-adapter",`Registering user-scoped MCP server: ${M} -> ${n} ${r.join(" ")}`);try{re(`${e} mcp remove -s user ${M}`,{encoding:"utf8",timeout:1e4,env:i,stdio:"pipe",windowsHide:!0})}catch{}const c=["mcp","add","--scope","user",M,"--",n,...r],u=process.platform==="win32"?'"':"'",h=re(`${e} ${c.map(p=>`${u}${p}${u}`).join(" ")}`,{encoding:"utf8",timeout:1e4,env:i,stdio:"pipe",windowsHide:!0});o.info("claude-adapter",`MCP server registered: ${h.trim()||"ok"}`)}resolveServerEntryPath(e){const t=f(e,"server","main.js");try{if(B(t))return t}catch{}const i=f(e,"dist","index.js");try{if(B(i))return i}catch{}throw new Error(`Cannot find grix-claude server entry in pluginDir: ${e}`)}clearActiveEvent(){const e=this.activeEvent;this.clearActiveEventIdleTimer(),this.clearActiveEventHardTimer(),this.clearActiveEventPostReplyTimer(),this.clearActiveEventPostReplyWatcher(),this.clearRescueSettleTimer(),e&&(this.snapshotAuditBoundary(e),this.sessionIdConflictRetriedEventIds.delete(e.eventId),this.malformedToolRetriedEventIds.delete(e.eventId),this.lostInjectionRetriedEventIds.delete(e.eventId),this.completedEventIds.set(e.eventId,Date.now()),this.lastClearedEvent={eventId:e.eventId,sessionId:e.sessionId,ts:Date.now()},this.emit(`reply:${e.sessionId}`,{status:"completed"}),this.emit("eventDone",e.eventId)),this.activeEvent=null,this.stopComposing(),this.resetSelfDrivenTextBaseline()}startComposing(e,t){}stopComposing(){}clearComposingTimer(){}resolveHookSignalsPath(){const e=y(),t=this.claudeCliSessionId||this.sessionId;return f(e.dataDir,`hook-signals-${t}.json`)}startActivityTracking(){this.activityManager&&this.activityManager.stop();const e=this.resolveHookSignalsPath(),t=y().hookSignalsLogPath,i=new kt(e,t);o.info("claude-adapter",`Activity tracking started: watching ${e}`),this.activityManager=new bt({hookSignalStore:i,onActivity:s=>this.onHookActivity(s),onTranscriptPath:s=>this.onHookTranscriptPath(s),onStop:()=>this.onClaudeTurnEnd("Stop"),onStopFailure:()=>this.onClaudeTurnEnd("StopFailure"),onCompactStart:()=>this.beginCompaction(),onCompactResult:()=>this.finishCompaction("post-compact-hook"),onPermissionRequest:(s,n)=>this.handlePermissionHookEvent(s,n),onUserPromptSubmit:s=>this.onUserPromptSubmit(s)}),this.activityManager.start()}onHookTranscriptPath(e){this.claudeCliSessionId&&jt(this.claudeCliSessionId,e)}onUserPromptSubmit(e){const t=this.activeEvent;!t||!e||e!==t.eventId||t.promptSubmittedAt!==void 0||(t.promptSubmittedAt=Date.now(),o.info("claude-adapter",`Prompt submitted for ${t.eventId}${t.channelPushedAt?` (${t.promptSubmittedAt-t.channelPushedAt}ms after push)`:""}`))}isChannelEventPrePush(e){return!e.singleTurnFallback&&e.channelPushedAt===void 0&&e.jsonlBaseOffset===void 0}isStopBeforePromptSubmit(e){return e.singleTurnFallback?!1:e.channelPushedAt===void 0?e.jsonlBaseOffset===void 0:e.promptSubmittedAt!==void 0?!1:Date.now()-e.channelPushedAt<fi}onClaudeTurnEnd(e){if(o.info("claude-adapter",`Hook activity: ${e}`),!this.activeEvent){this.stopHookBarrierSessionId&&(o.info("claude-adapter",`Late Stop hook after end_turn finalize \u2014 releasing barrier (session=${this.stopHookBarrierSessionId})`),this.clearStopHookBarrier()),this.deliverSelfDrivenText(e);return}if(this.activeEvent.toolCallInFlight){o.info("claude-adapter",`Stop hook deferred: toolCallInFlight for ${this.activeEvent.eventId}`),this.activeEvent.pendingStopHook=e;return}if(!this.isEventAnswered()&&this.isStopBeforePromptSubmit(this.activeEvent)){o.warn("claude-adapter",`${e} hook ignored for ${this.activeEvent.eventId}: arrived before this event's prompt was submitted (${Date.now()-(this.activeEvent.channelPushedAt??0)}ms after push) \u2014 startup/resume turn tail`);return}if(this.activeEvent.singleTurnFallback&&!this.isEventAnswered()){o.info("claude-adapter",`${e} hook ignored for ${this.activeEvent.eventId}: single-turn fallback owns finalization`);return}this.finalizeActiveEvent(e)}resolveIncrementalTextFromOffset(e,t){if(!this.isChannelEventPrePush(t)){if(t.textForwardOffset!==void 0)return t.textForwardOffset;if(t.jsonlBaseOffset!==void 0)return t.jsonlBaseOffset;if(t.singleTurnFallback){if(!this.claudeCliSessionId||!this.claudeSessionCwd)return;const i=O(this.claudeCliSessionId,this.claudeSessionCwd);return S(i)?0:void 0}}}forwardIncrementalText(e){const t=this.activeEvent;if(!t||t.eventId!==e||this.isChannelEventPrePush(t)||t.replied||!this.claudeCliSessionId||!this.claudeSessionCwd)return;const i=this.resolveIncrementalTextFromOffset(e,t);if(i===void 0)return;const{segments:s,newOffset:n,apiError:r,apiErrorText:a}=De(this.claudeCliSessionId,this.claudeSessionCwd,i);if(t.textForwardOffset=n,r&&(a&&(t.apiErrorText=a),t.apiFormatError||(t.apiFormatError=!0,o.error("claude-adapter",`API error entry detected in JSONL for ${e} (isApiErrorMessage), not forwarding its text as reply`))),s.length===0)return;const l=t.sessionId;for(const c of s){const u=c.trim();if(!u)continue;const h=`txt_${e}_${this.textForwardSeq++}`;this.bridgeCallbacks.sendStreamChunk(e,l,u,1,!1,h),this.bridgeCallbacks.sendStreamChunk(e,l,"",2,!0,h),t.textForwarded||(t.textForwarded=!0,t.repliedAt===void 0&&(t.repliedAt=Date.now()),this.startPostReplyDeadline(e),this.startPostReplyJsonlWatcher(e))}this.markActiveEventActivity(e,l),o.info("claude-adapter",`Forwarded ${s.length} plain-text segment(s) for ${e}`)}flushForwardedTail(e){this.activeEvent?.eventId===e&&this.activeEvent.textForwarded&&this.forwardIncrementalText(e)}isEventAnswered(e=this.activeEvent){return!!(e&&(e.replied||e.textForwarded))}attemptRescueFromJsonl(e,t){if(!this.claudeCliSessionId||!this.claudeSessionCwd)return o.info("claude-adapter",`Rescue skipped: no claudeCliSessionId or claudeSessionCwd for ${e}`),"none";const i=this.activeEvent?.jsonlBaseOffset,s=W(this.claudeCliSessionId,this.claudeSessionCwd,i);if(s?.apiError)return o.error("claude-adapter",`Rescue aborted for ${e}: turn ended with a CLI API error entry (isApiErrorMessage)`),this.activeEvent?.eventId===e&&(this.activeEvent.apiFormatError=!0,s.apiErrorText&&(this.activeEvent.apiErrorText=s.apiErrorText)),"none";if(s?.metaOnly)return i===void 0?(o.warn("claude-adapter",`Rescue for ${e}: only CLI meta text found but jsonlBaseOffset is unset \u2014 cannot attribute it to this turn, treating as unanswered`),"none"):(o.info("claude-adapter",`Rescue skipped for ${e}: this turn wrote only CLI meta text, finalizing silently`),"silent");const n=s?.text||null;if(!n)return o.info("claude-adapter",`Rescue failed: no assistant text found in JSONL for ${e}`),"none";const r=`rescue_${e}_${Date.now()}`;return this.bridgeCallbacks.sendStreamChunk(e,t,n,1,!1,r),this.bridgeCallbacks.sendStreamChunk(e,t,"",2,!0,r),o.info("claude-adapter",`Rescue succeeded for event ${e}: sent ${n.length} chars from JSONL`),"sent"}warnUnsentFinalIfAny(e){if(!this.claudeCliSessionId||!this.claudeSessionCwd)return;const t=this.activeEvent?.jsonlBaseOffset,i=W(this.claudeCliSessionId,this.claudeSessionCwd,t);if(!i||i.stopReason!=="end_turn")return;const s=i.text?.trim()??"";if(!s)return;const n=(this.activeEvent?.lastReplyText??"").trim();n&&(s===n||n.includes(s))||o.warn("claude-adapter",`Unsent final end_turn text for ${e} (${s.length} chars) not delivered \u2014 \u5DF2\u5E94\u7B54\u8FC7\uFF0C\u6309\u7B56\u7565\u4E0D\u8865\u53D1\uFF08\u7591\u4F3C\u56DE\u5408\u6536\u5C3E\u81EA\u8FF0\uFF1B\u82E5\u786E\u4E3A\u6F0F\u53D1\u7ED3\u8BBA\u53EF\u636E\u6B64\u6392\u67E5\uFF09`)}finalizeActiveEvent(e){if(!this.activeEvent)return;const t=this.activeEvent.eventId,i=this.activeEvent.sessionId;if(this.isEventAnswered()&&this.activeEvent.jsonlBaseOffset!==void 0&&this.claudeCliSessionId&&this.claudeSessionCwd){const s=W(this.claudeCliSessionId,this.claudeSessionCwd,this.activeEvent.jsonlBaseOffset);if(s?.apiError)o.warn("claude-adapter",`Stop hook for ${t}: turn ended with a CLI API error entry, finalizing without waiting for end_turn`);else if(!s||s.stopReason!=="end_turn")if($e(this.claudeCliSessionId,this.claudeSessionCwd,this.activeEvent.jsonlBaseOffset))o.info("claude-adapter",`Stop hook: terminal tool_result detected for ${t} (grix_reply as final action, no end_turn expected), finalizing as responded`);else{o.info("claude-adapter",`Stop hook suppressed for ${t}: no end_turn in JSONL after offset=${this.activeEvent.jsonlBaseOffset} \u2014 likely resume-drain hook, waiting for JSONL watcher`),this.markActiveEventActivity(t,i);return}}if(this.flushForwardedTail(t),!this.activeEvent.apiFormatError){if(this.activeEvent.replied||this.activeEvent.textForwarded){this.activeEvent.replied&&!this.activeEvent.textForwarded&&this.warnUnsentFinalIfAny(t),this.finishTurnEnd(t,i,e,!0,`replied=${this.activeEvent.replied}, textForwarded=${this.activeEvent.textForwarded}`);return}if(this.activeEvent.rescueSettleInFlight){o.info("claude-adapter",`${e} ignored for ${t}: rescue retry already in flight`);return}const s=this.recoverMalformedToolCall(t);if(s==="retried")return;if(s==="exhausted"){this.bridgeCallbacks.sendStreamChunk(t,i,si,1,!1,`mtc_${t}`),this.bridgeCallbacks.sendStreamChunk(t,i,"",2,!0,`mtc_${t}`),this.finishTurnEnd(t,i,e,!0,"malformed-toolcall-fallback");return}const n=this.attemptRescueFromJsonl(t,i);if(n!=="none"){this.finishTurnEnd(t,i,e,!0,n==="silent"?"no-response-requested":"rescued");return}if(this.activeEvent?.apiFormatError){this.finishTurnEnd(t,i,e,!1,"apiFormatError (detected during rescue)");return}this.beginRescueSettleWait(t,i,e);return}this.finishTurnEnd(t,i,e,!1,"apiFormatError")}beginRescueSettleWait(e,t,i){if(!this.activeEvent||this.activeEvent.eventId!==e)return;this.activeEvent.rescueSettleInFlight=!0;const s=Math.max(1,Math.ceil(Je/We));let n=0;const r=()=>{if(this.rescueSettleTimer=null,this.stopped)return;const a=this.activeEvent;if(!a||a.eventId!==e)return;if(n++,a.replied||a.textForwarded){this.finishTurnEnd(e,t,i,!0,`replied=${a.replied}, textForwarded=${a.textForwarded} (settled during flush wait)`);return}if(a.apiFormatError){this.finishTurnEnd(e,t,i,!1,"apiFormatError (settled during flush wait)");return}const l=this.attemptRescueFromJsonl(e,t);if(l!=="none"){this.finishTurnEnd(e,t,i,!0,l==="silent"?"no-response-requested-after-settle":"rescued-after-settle");return}if(a.apiFormatError){this.finishTurnEnd(e,t,i,!1,"apiFormatError (detected during rescue after settle)");return}if(i==="Stop"&&this.isToolOnlyTurnConcluded(a)){this.finishTurnEnd(e,t,i,!0,`tool-only-turn (toolCalls=${a.toolActivityCount??0})`);return}if(n>=s){this.finishTurnEnd(e,t,i,!1,`rescue-failed-after-settle (waited ~${Math.round(Je/1e3)}s, no transcript output for this turn)`);return}this.markActiveEventActivity(e,t),this.rescueSettleTimer=setTimeout(r,We)};this.rescueSettleTimer=setTimeout(r,Xt)}isToolOnlyTurnConcluded(e){return!e.toolActivityCount||e.jsonlBaseOffset===void 0||!this.claudeCliSessionId||!this.claudeSessionCwd?!1:ce(this.claudeCliSessionId,this.claudeSessionCwd,e.jsonlBaseOffset).lastStopReason==="end_turn"}clearRescueSettleTimer(){this.rescueSettleTimer&&(clearTimeout(this.rescueSettleTimer),this.rescueSettleTimer=null)}finishTurnEnd(e,t,i,s,n){if(s)o.info("claude-adapter",`Stop hook received, finalizing event ${e} as responded (${n})`),this.bridgeCallbacks.sendEventResult(e,"responded");else{o.warn("claude-adapter",`Active event not confirmed when ${i}: ${e} (${n}), sending failed result`);const r=this.activeEvent?.apiFormatError?de(this.activeEvent.apiErrorText):"Claude exited before completing its reply. Please try again.";this.bridgeCallbacks.sendStreamChunk(e,t,r,1,!1,`err_${e}`),this.bridgeCallbacks.sendStreamChunk(e,t,"",2,!0,`err_${e}`),this.bridgeCallbacks.sendEventResult(e,"failed",r,"agent_stop_failure",!0)}this.clearActiveEvent(),this.armStopHookBarrier(t)}onHookActivity(e){const t=this.activeEvent?.sessionId;if(o.info("claude-adapter",`Hook activity: tool=${e?.tool_name??"(clear)"} session=${t??"(none)"}`),e&&this.activeEvent&&!this.isChannelEventPrePush(this.activeEvent)&&(this.activeEvent.toolActivityCount=(this.activeEvent.toolActivityCount??0)+1,this.markActiveEventActivity(this.activeEvent.eventId,this.activeEvent.sessionId),this.forwardIncrementalText(this.activeEvent.eventId),(this.activeEvent.replied||this.activeEvent.textForwarded)&&this.startPostReplyDeadline(this.activeEvent.eventId)),e&&!this.activeEvent&&!this.compacting&&this.noteSelfDrivenActivity(e),!!t)if(e){this.startComposing(t,e);const i=this.activeEvent;if(i&&!this.isChannelEventPrePush(i)){const s=e.event_name,n=e.tool_name,r=e.tool_input??"",a=n?Ot(n):!1;if(n==="ExitPlanMode"&&s==="PreToolUse")o.info("claude-adapter","ExitPlanMode detected; waiting for user decision via permission card");else if(n==="AskUserQuestion"&&s==="PreToolUse")this.handleAskUserQuestion(i,r);else if(s==="PreToolUse"&&n)a||this.bridgeCallbacks.sendToolUse(i.eventId,i.sessionId,n,r);else if((s==="PostToolUse"||s==="PostToolUseFailure")&&n){if(n==="AskUserQuestion")return;!a&&s==="PostToolUseFailure"&&this.bridgeCallbacks.sendToolResult(i.eventId,i.sessionId,n,"(failed)")}}}else this.startComposing(t)}handlePermissionHookEvent(e,t){if(!this.activeEvent){o.warn("claude-adapter","PermissionRequest without active event, ignoring");return}if(e==="AskUserQuestion"){o.info("claude-adapter","Skip permission card for AskUserQuestion; handled by agent_question card flow");return}const i=this.activeEvent,s=y();new G(s.permissionRequestsDir).listPending().then(r=>{const a=r.length>0?r[r.length-1]:null;if(!a){o.warn("claude-adapter","No pending permission request found in store");return}const l=a.request_id;this.pendingPermissions.set(l,{eventId:i.eventId,sessionId:i.sessionId});const c=typeof t=="string"?t:"";if(e==="ExitPlanMode"){const p=zt(c);p&&this.bridgeCallbacks.sendReply(i.eventId,i.sessionId,p)}const u=e==="ExitPlanMode"?"":c.slice(0,100),h=u?`${e}: ${u}`:e;this.bridgeCallbacks.sendPermissionCard({eventId:i.eventId,sessionId:i.sessionId,approvalId:l,toolName:e,toolTitle:h}),o.info("claude-adapter",`Sent permission card: approvalId=${l} tool=${e}`)}).catch(r=>{o.warn("claude-adapter",`Failed to send permission card: ${r}`)})}async awaitPendingQuestionRequest(e,t){const i=Date.now()+Ne;for(;;){const s=await e.listPending(),n=Date.now(),r=[...s].reverse().filter(l=>String(l.session_id??"").trim()===this.claudeCliSessionId&&n-Number(l.created_at??0)<=Fe),a=r.find(l=>String(l.event_id??"").trim()===t)??r.find(l=>String(l.event_id??"").trim()==="")??r[0]??null;if(a||Date.now()>=i)return a;await new Promise(l=>setTimeout(l,Kt))}}handleAskUserQuestion(e,t){const i=xe(t);if(!i){o.warn("claude-adapter","Failed to parse AskUserQuestion input, skipping agent_question card");return}const s=y(),n=new ke(s.questionRequestsDir);this.awaitPendingQuestionRequest(n,e.eventId).then(async r=>{const a=r?.request_id??`fallback-${Date.now()}`;r||o.warn("claude-adapter",`No pending question waiter after ${Ne}ms; sending card with ${a}. Submitting it will report not_pending and fall back to a plain message.`),r&&String(r.event_id??"").trim()===""&&await n.updateRequest(r.request_id,{event_id:e.eventId}),this.pendingQuestion.set(a,{eventId:e.eventId,sessionId:e.sessionId}),e.awaitingUserQuestion=!0;const c=(Number(r?.created_at??0)>0?Number(r?.created_at):Date.now())+Me;this.bridgeCallbacks.sendAgentQuestionCard(e.eventId,e.sessionId,{request_id:a,mode:"form",questions:i,expires_at:c}),o.info("claude-adapter",`Sent agent_question card: request_id=${a} questions=${i.length}`)}).catch(()=>{o.warn("claude-adapter","Failed to list pending questions from store")})}parseAskUserQuestions(e){return xe(e)}parseAskUserQuestionInput(e){let t;try{t=JSON.parse(e)}catch{return null}const i=[],s=t.questions;if(!Array.isArray(s)||s.length===0)return null;for(let n=0;n<s.length;n++){const r=s[n];if(!r||typeof r!="object")continue;const a=String(r.header??r.question??`Question ${n+1}`),l=String(r.prompt??""),c=Array.isArray(r.options)?r.options:void 0,u=r.multiSelect===!0;i.push({header:a,prompt:l,...c&&c.length>0?{options:c}:{},...u?{multi_select:!0}:{}})}return i.length>0?{questions:i}:null}pruneCompletedEvents(){const e=Date.now()-Vt;for(const[t,i]of this.completedEventIds.entries())i<e&&this.completedEventIds.delete(t)}markActiveEventActivity(e,t){const i=this.activeEvent;i&&(e&&i.eventId!==e||t&&i.sessionId!==t||(this.resetActiveEventIdleTimer(i.eventId),this.isEventAnswered(i)||this.resetActiveEventHardTimer(i.eventId)))}clearActiveEventIdleTimer(){this.activeEventIdleTimer&&(clearTimeout(this.activeEventIdleTimer),this.activeEventIdleTimer=null)}clearActiveEventHardTimer(){this.activeEventHardTimer&&(clearTimeout(this.activeEventHardTimer),this.activeEventHardTimer=null)}shouldExtendByLiveness(e){const t=this.activeEvent;if(!t||t.eventId!==e||!this.claudeCliSessionId||!this.claudeSessionCwd)return!1;const i=this.singleTurnFallbackChild,s=!!i&&i.exitCode===null&&!i.killed;if(!this.claudeProcess&&!this.claudePty&&!s)return!1;if(this.lastPtyOutputAt>0&&Date.now()-this.lastPtyOutputAt<Ke)return t.livenessExtendStartAt=Date.now(),!0;const n=ce(this.claudeCliSessionId,this.claudeSessionCwd,t.jsonlBaseOffset);if(n.freshMs===null||!(t.jsonlBaseOffset===void 0?n.freshMs<ue:n.lastStopReason!==null?n.lastStopReason!=="end_turn":n.freshMs<ue))return!1;if(n.freshMs<ue)t.livenessExtendStartAt=Date.now();else{const a=t.livenessExtendStartAt??Date.now();if(t.livenessExtendStartAt===void 0&&(t.livenessExtendStartAt=a),Date.now()-a>Ye)return o.warn("claude-adapter",`Liveness extension budget exhausted for ${e} (no JSONL writes for ${Math.round((Date.now()-a)/6e4)}min), allowing close`),!1}return o.info("claude-adapter",`Liveness check: turn in progress for ${e} (lastStopReason=${n.lastStopReason??"none"}, freshMs=${n.freshMs}), extending`),!0}captureEventJsonlBaseOffset(e,t){this.captureAuditTranscriptStart(e);const i=this.activeEvent;if(!(!i||i.eventId!==e)&&!(!t?.force&&i.jsonlBaseOffset!==void 0)&&!(!this.claudeCliSessionId||!this.claudeSessionCwd))try{const s=O(this.claudeCliSessionId,this.claudeSessionCwd);if(i.auditTranscriptPath===s&&i.auditTranscriptStartOffset!==void 0){i.jsonlBaseOffset=i.auditTranscriptStartOffset;return}i.jsonlBaseOffset=S(s)?I(s).size:0}catch{i.jsonlBaseOffset=0}}captureSingleTurnFallbackJsonlBaseOffset(e){this.captureAuditTranscriptStart(e);const t=this.activeEvent;if(!(!t||t.eventId!==e||t.jsonlBaseOffset!==void 0)&&!(!this.claudeCliSessionId||!this.claudeSessionCwd))try{const i=O(this.claudeCliSessionId,this.claudeSessionCwd);if(!S(i))return;if(t.auditTranscriptPath===i&&t.auditTranscriptStartOffset!==void 0){t.jsonlBaseOffset=t.auditTranscriptStartOffset;return}t.jsonlBaseOffset=I(i).size}catch{}}captureAuditTranscriptStart(e){const t=this.activeEvent;if(!t||t.eventId!==e||t.rawEvent?.audit?.enabled!==!0||t.auditTranscriptStartOffset!==void 0||!this.claudeCliSessionId)return;const i=this.claudeSessionCwd||process.cwd(),s=O(this.claudeCliSessionId,i);t.auditTranscriptPath=s,t.auditClaudeSessionId=this.claudeCliSessionId;try{t.auditTranscriptStartOffset=S(s)?I(s).size:0}catch{t.auditTranscriptStartOffset=0}const n=this.auditEvidencePaths;n&&(t.auditOtelLogsStartOffset=Q(n.logsPath),t.auditOtelTracesStartOffset=Q(n.tracesPath),n.rawProviderBody&&(t.auditRawBodyFilesBefore=ze(n.rawBodyDir)))}snapshotAuditBoundary(e){if(e.rawEvent?.audit?.enabled!==!0)return;if(e.auditTranscriptStartOffset===void 0||!e.auditTranscriptPath||!e.auditClaudeSessionId){this.completedAuditBoundaries.set(e.eventId,Object.freeze({adapterType:this.type}));return}const t=this.claudeSessionCwd?O(e.auditClaudeSessionId,this.claudeSessionCwd):e.auditTranscriptPath,i=S(t)?t:e.auditTranscriptPath;let s=e.auditTranscriptStartOffset;try{s=S(i)?I(i).size:e.auditTranscriptStartOffset}catch{}const n=St(i),r=this.auditEvidencePaths,a=new Set(e.auditRawBodyFilesBefore??[]),l=r?.rawProviderBody?ze(r.rawBodyDir).filter(u=>!a.has(u)):[],c=Object.freeze({adapterType:this.type,providerSessionId:e.auditClaudeSessionId,transcriptPath:i,transcriptStartOffset:e.auditTranscriptStartOffset,transcriptEndOffset:s,...e.auditSingleTurnFallback?{singleTurnFallback:!0}:{},subagentRoot:f(n,e.auditClaudeSessionId,"subagents"),...r?{otelLogsPath:r.logsPath,otelLogsStartOffset:e.auditOtelLogsStartOffset??0,otelLogsEndOffset:Q(r.logsPath),otelTracesPath:r.tracesPath,otelTracesStartOffset:e.auditOtelTracesStartOffset??0,otelTracesEndOffset:Q(r.tracesPath),rawApiBodyDirectory:r.rawBodyDir,rawApiBodyPaths:l}:{}});this.completedAuditBoundaries.set(e.eventId,c)}noteSelfDrivenActivity(e){!this.selfDrivenActive&&this.lastClearedEvent&&Date.now()-this.lastClearedEvent.ts<Qe||(this.selfDrivenLastSignalAt=Date.now(),e&&(this.selfDrivenLabel=ci(e)),!this.selfDrivenActive&&(this.selfDrivenActive=!0,o.info("claude-adapter",`Self-driven activity detected for session ${this.sessionId} (no active event), debouncing before reporting working state`),this.selfDrivenReportTimer=setTimeout(()=>{this.selfDrivenReportTimer=null,!(!this.selfDrivenActive||this.stopped||this.activeEvent)&&(this.selfDrivenReported=!0,o.info("claude-adapter",`Self-driven working state confirmed for session ${this.sessionId} (sustained >${Ve/1e3}s), reporting`),this.emit("sessionActivity",this.sessionId??"",!0,this.selfDrivenLabel??void 0))},Ve),this.selfDrivenReportTimer.unref(),this.selfDrivenSweepTimer=setInterval(()=>this.sweepSelfDriven(),ai),this.selfDrivenSweepTimer.unref()))}sweepSelfDriven(){if(!this.selfDrivenActive)return;if(this.stopped||this.activeEvent){this.stopSelfDriven();return}const e=Date.now()-this.selfDrivenLastSignalAt;if(this.claudeCliSessionId&&this.claudeSessionCwd){const t=ce(this.claudeCliSessionId,this.claudeSessionCwd),i=t.lastStopReason!==null&&t.lastStopReason!=="end_turn",s=t.freshMs!==null&&t.freshMs<Ye,n=Nt(this.claudeCliSessionId,this.claudeSessionCwd)>0;if((i||n)&&s){this.selfDrivenReported&&this.emit("sessionActivity",this.sessionId??"",!0,this.selfDrivenLabel??void 0);return}}if(e<ri){this.selfDrivenReported&&this.emit("sessionActivity",this.sessionId??"",!0,this.selfDrivenLabel??void 0);return}o.info("claude-adapter",`Self-driven activity ended for session ${this.sessionId} (quiet for ${Math.round(e/1e3)}s, no pending work)`),this.stopSelfDriven()}stopSelfDriven(){this.selfDrivenSweepTimer&&(clearInterval(this.selfDrivenSweepTimer),this.selfDrivenSweepTimer=null),this.selfDrivenReportTimer&&(clearTimeout(this.selfDrivenReportTimer),this.selfDrivenReportTimer=null),this.selfDrivenActive&&(this.selfDrivenActive=!1,this.selfDrivenLabel=null,this.selfDrivenReported&&(this.selfDrivenReported=!1,this.emit("sessionActivity",this.sessionId??"",!1)))}currentTranscriptSize(){if(!this.claudeCliSessionId||!this.claudeSessionCwd)return null;try{return I(O(this.claudeCliSessionId,this.claudeSessionCwd)).size}catch{return null}}resetSelfDrivenTextBaseline(){this.clearSelfDrivenDeliverTimer(),this.selfDrivenReplied=!1,this.selfDrivenTextOffset=this.currentTranscriptSize()}clearSelfDrivenDeliverTimer(){this.selfDrivenDeliverTimer&&(clearTimeout(this.selfDrivenDeliverTimer),this.selfDrivenDeliverTimer=null)}deliverSelfDrivenText(e){if(this.stopped||this.activeEvent)return;this.clearSelfDrivenDeliverTimer();const t=this.currentTranscriptSize();if(t===null)return;if(this.selfDrivenTextOffset===null){this.selfDrivenTextOffset=t;return}if(this.lastClearedEvent&&Date.now()-this.lastClearedEvent.ts<Qe){this.selfDrivenTextOffset=t;return}if(this.selfDrivenReplied){this.selfDrivenReplied=!1,this.selfDrivenTextOffset=t;return}const i=Date.now()+li,s=()=>{if(this.selfDrivenDeliverTimer=null,!(this.stopped||this.activeEvent)&&!this.flushSelfDrivenText(e)){if(Date.now()>=i){o.info("claude-adapter",`Self-driven ${e}: no new transcript text to deliver for session ${this.sessionId}`);return}this.selfDrivenDeliverTimer=setTimeout(s,oi),this.selfDrivenDeliverTimer.unref()}};s()}flushSelfDrivenText(e){if(!this.claudeCliSessionId||!this.claudeSessionCwd)return!1;const t=this.selfDrivenTextOffset??0,{segments:i,newOffset:s}=De(this.claudeCliSessionId,this.claudeSessionCwd,t);this.selfDrivenTextOffset=s;const n=i.map(a=>a.trim()).filter(a=>a.length>0);if(n.length===0)return!1;const r=this.sessionId;if(!r)return o.warn("claude-adapter",`Self-driven ${e}: ${n.length} text segment(s) ready but session id is unknown, dropping`),!0;for(const a of n)Re(this.getEventToolHandle(),"grix_reply",{session_id:r,text:a});return o.info("claude-adapter",`Self-driven ${e}: delivered ${n.length} text segment(s) to session ${r}`),!0}tryRecoverLostInjection(e){const t=this.activeEvent;if(this.stopped||!t||t.eventId!==e||this.isEventAnswered(t)||t.channelPushedAt===void 0||t.promptSubmittedAt!==void 0||this.compacting||this.stopHookBarrierSessionId===t.sessionId||this.lostInjectionRetriedEventIds.has(e))return!1;this.lostInjectionRetriedEventIds.add(e);const i=t.rawEvent;return o.warn("claude-adapter",`Lost injection for ${e}: pushed ${Date.now()-t.channelPushedAt}ms ago but prompt never submitted \u2014 respawning Claude and re-pushing the event once`),this.clearActiveEventIdleTimer(),this.clearActiveEventHardTimer(),this.activeEvent=null,this.stopComposing(),this.killClaudeProcess(Be),this.deliverInboundEvent(i),!0}resetActiveEventIdleTimer(e){this.clearActiveEventIdleTimer(),this.activeEventIdleTimer=setTimeout(()=>{if(!this.stopped&&this.activeEvent?.eventId===e){if(this.activeEvent?.toolCallInFlight){o.info("claude-adapter",`Idle timeout skipped: toolCallInFlight for ${e}, resetting timer`),this.resetActiveEventIdleTimer(e);return}if(this.activeEvent?.awaitingUserQuestion){o.info("claude-adapter",`Idle timeout skipped: awaitingUserQuestion for ${e}, resetting timer`),this.resetActiveEventIdleTimer(e);return}if(this.pendingPermissions.size>0){o.info("claude-adapter",`Idle timeout skipped: pendingPermissions=${this.pendingPermissions.size} for ${e}, resetting timer`),this.resetActiveEventIdleTimer(e);return}if(this.shouldExtendByLiveness(e)){this.resetActiveEventIdleTimer(e);return}this.tryRecoverLostInjection(e)||this.finalizeStuckActiveEvent(e,"idle")}},je)}finalizeStuckActiveEvent(e,t){const i=this.isEventAnswered(),s=i?"responded":"failed",n=t==="idle"?je:Ue,r=!i&&this.activeEvent?.apiFormatError?de(this.activeEvent.apiErrorText):void 0,a=i?void 0:r??(t==="idle"?`agent idle for ${n/1e3}s`:`agent exceeded max duration ${n/1e3}s`),l=i?void 0:t==="idle"?"agent_idle_timeout":"agent_hard_timeout",c=this.lostInjectionRetriedEventIds.has(e)?", after lost-injection retry":"";o.error("claude-adapter",`Active event ${t} timeout (${n/1e3}s): ${e}, answered=${i}${c}, sending ${s}`),this.flushForwardedTail(e),this.completedEventIds.set(e,Date.now());let u=!1;r&&this.activeEvent&&(this.bridgeCallbacks.sendStreamChunk(e,this.activeEvent.sessionId,r,1,!1,`err_${e}`),this.bridgeCallbacks.sendStreamChunk(e,this.activeEvent.sessionId,"",2,!0,`err_${e}`),u=!0),u?this.bridgeCallbacks.sendEventResult(e,s,a,l,!0):this.bridgeCallbacks.sendEventResult(e,s,a,l),this.clearActiveEvent(),s==="failed"&&this.emit("stuck")}resetActiveEventHardTimer(e){this.clearActiveEventHardTimer(),this.activeEventHardTimer=setTimeout(()=>{if(!this.stopped&&this.activeEvent?.eventId===e){if(this.shouldExtendByLiveness(e)){this.resetActiveEventHardTimer(e);return}this.finalizeStuckActiveEvent(e,"hard")}},Ue)}clearActiveEventPostReplyTimer(){this.activeEventPostReplyTimer&&(clearTimeout(this.activeEventPostReplyTimer),this.activeEventPostReplyTimer=null)}clearActiveEventPostReplyWatcher(){if(this.activeEventPostReplyWatcher){try{this.activeEventPostReplyWatcher.close()}catch{}this.activeEventPostReplyWatcher=null}this.activeEventPostReplyPoll&&(clearInterval(this.activeEventPostReplyPoll),this.activeEventPostReplyPoll=null)}startPostReplyJsonlWatcher(e){if(this.clearActiveEventPostReplyWatcher(),!this.claudeCliSessionId||!this.claudeSessionCwd)return;const t=O(this.claudeCliSessionId,this.claudeSessionCwd);if(!this.activeEvent?.sessionId)return;if(!S(t)){o.info("claude-adapter",`JSONL watcher skipped: file not yet available for ${e}`);return}const s=I(t).size;this.activeEvent&&(this.activeEvent.jsonlBaseOffset=s);let n=null;try{this.activeEventPostReplyWatcher=Et(t,()=>{n&&clearTimeout(n),n=setTimeout(()=>this.finalizeIfEndTurn(e),300)}),this.activeEventPostReplyPoll=setInterval(()=>this.finalizeIfEndTurn(e),ni),o.info("claude-adapter",`JSONL watcher started for ${e} at ${t} (baseOffset=${s})`)}catch(r){o.warn("claude-adapter",`JSONL watcher start failed for ${e}: ${r}`)}}finalizeIfEndTurn(e){if(this.stopped||this.activeEvent?.eventId!==e||this.isChannelEventPrePush(this.activeEvent)||!this.claudeCliSessionId||!this.claudeSessionCwd)return;const t=W(this.claudeCliSessionId,this.claudeSessionCwd,this.activeEvent.jsonlBaseOffset);if(t?.apiError){this.activeEvent.apiFormatError=!0,t.apiErrorText&&(this.activeEvent.apiErrorText=t.apiErrorText);return}if(t&&t.stopReason==="end_turn"){o.info("claude-adapter",`JSONL watcher: end_turn detected for ${e}, finalizing as responded`);const i=this.activeEvent.sessionId;this.flushForwardedTail(e),this.bridgeCallbacks.sendEventResult(e,"responded",void 0,void 0),this.clearActiveEvent(),this.armStopHookBarrier(i);return}if(this.activeEvent?.replied&&this.activeEvent.jsonlBaseOffset!==void 0&&$e(this.claudeCliSessionId,this.claudeSessionCwd,this.activeEvent.jsonlBaseOffset,!0)){if(this.lastPtyOutputAt>0&&Date.now()-this.lastPtyOutputAt<Ke)return;o.info("claude-adapter",`JSONL poll: terminal tool_result detected for ${e} (file quiet, no end_turn), finalizing as responded`),this.flushForwardedTail(e),this.bridgeCallbacks.sendEventResult(e,"responded",void 0,void 0),this.clearActiveEvent()}}startPostReplyDeadline(e){this.clearActiveEventPostReplyTimer(),this.clearActiveEventHardTimer(),this.activeEventPostReplyTimer=setTimeout(()=>{if(this.stopped||this.activeEvent?.eventId!==e||!this.activeEvent?.replied&&!this.activeEvent?.textForwarded)return;if(this.shouldExtendByLiveness(e)){this.startPostReplyDeadline(e);return}const t=this.activeEvent.repliedAt,i=Math.round((Date.now()-t)/1e3);o.info("claude-adapter",`Post-reply deadline reached for ${e} (${i}s since reply), completing as responded`),this.flushForwardedTail(e),this.bridgeCallbacks.sendEventResult(e,"responded",void 0,void 0),this.clearActiveEvent()},ei)}beginCompaction(){this.compacting||(this.compacting=!0,o.info("claude-adapter","Compaction started; gating input"),this.emit("pauseIntake","compaction"),this.resetCompactingActivityTimer())}resetCompactingActivityTimer(){this.compactingTimer&&clearTimeout(this.compactingTimer),this.compactingTimer=setTimeout(()=>{o.warn("claude-adapter","Compaction stall: no PTY activity for 90s, emitting stuck"),this.emit("stuck")},9e4),this.compactingTimer.unref?.()}finishCompaction(e){if(!this.compacting)return;this.compacting=!1,this.compactingTimer&&(clearTimeout(this.compactingTimer),this.compactingTimer=null),o.info("claude-adapter",`Compaction finished (${e}); resuming intake`),this.emit("resumeIntake","compaction");const t=this.compactionDoneResolver;this.compactionDoneResolver=null,t?.(e)}armStopHookBarrier(e){this.stopHookBarrierTimer&&clearTimeout(this.stopHookBarrierTimer),this.stopHookBarrierSessionId=e,this.emit("pauseIntake","barrier"),this.stopHookBarrierTimer=setTimeout(()=>{this.stopHookBarrierSessionId===e&&(o.warn("claude-adapter",`Stop hook barrier timeout for session=${e}`),this.clearStopHookBarrier())},hi)}clearStopHookBarrier(){const e=this.stopHookBarrierSessionId!==null;this.stopHookBarrierSessionId=null,this.stopHookBarrierTimer&&(clearTimeout(this.stopHookBarrierTimer),this.stopHookBarrierTimer=null),e&&this.emit("resumeIntake","barrier")}clearPendingMcpFailureTimer(){this.pendingMcpFailureTimer&&(clearTimeout(this.pendingMcpFailureTimer),this.pendingMcpFailureTimer=null)}markMcpStartupFailure(){if(!this.mcpStartupFailureHandled&&(this.mcpStartupFailureHandled=!0,this.mcpChannelBroken=!0,o.error("claude-adapter","Claude reported blocking MCP server startup failure"),this.activeEvent)){const e=this.activeEvent.eventId,t=this.activeEvent.sessionId;this.bridgeCallbacks.sendStreamChunk(e,t,`
17
+ `)}stopMcpServer(){if(this.mcpServerReady=!1,this.notifySocket){try{this.notifySocket.destroy()}catch{}this.notifySocket=null}if(this.mcpServerProcess){try{this.mcpServerProcess.kill("SIGTERM")}catch{}this.mcpServerProcess=null}}releaseNotifyPortReservation(){Ti(this.notifyPort),this.notifyPort=0}killClaudeProcess(e){const t=this.claudeProcess,i=this.claudePty,s=this.claudeChildPid;if(e==="single-turn fallback isolation"&&(t&&this.singleTurnFallbackProcesses.add(t),i&&this.singleTurnFallbackPtys.add(i)),e===Be&&(t&&this.lostInjectionKilledProcesses.add(t),i&&this.lostInjectionKilledPtys.add(i)),this.claudeProcess=null,this.claudePty=null,this.claudeChildPid=0,this.spawnPromise=null,this.alive=!1,this.stopMcpServer(),this.releaseNotifyPortReservation(),this.mcpChannelBroken=!1,this.channelGateClosed=!1,this.startupChannelListening=!1,this.startupChannelListeningAt=0,this.deferredModelId=null,this.clearPendingMcpFailureTimer(),this.clearWindowsTrustDialogTimer(),this.windowsTrustDialogAnswered=!1,this.sessionIdConflictDetected=!1,this.pendingPermissions.size>0){const n=y(),r=new z(n.permissionRequestsDir);for(const[a]of this.pendingPermissions)r.resolveRequest(a,"deny").catch(()=>{});this.pendingPermissions.clear()}if(i)try{i.kill()}catch{}if(t?.pid&&q(t,"SIGTERM"),s>0)try{process.kill(s,"SIGTERM")}catch{}if(t?.pid||i||s>0){const n=s,r=t,a=i;setTimeout(()=>{if(a)try{a.kill()}catch{}if(r?.pid&&q(r,"SIGKILL"),n>0)try{process.kill(n,"SIGKILL")}catch{}},5e3).unref()}o.info("claude-adapter",`Claude process killed (reason=${e}, pid=${s}, expectPid=${t?.pid})`)}tryRecoverSessionIdConflict(){if(!this.sessionIdConflictDetected||this.stopped||!this.activeEvent||this.isEventAnswered())return!1;const e=this.activeEvent.eventId;if(this.sessionIdConflictRetriedEventIds.has(e))return this.sessionIdConflictDetected=!1,!1;this.sessionIdConflictRetriedEventIds.add(e),this.sessionIdConflictDetected=!1;const t=this.activeEvent.rawEvent;return o.warn("claude-adapter",`Detected Claude session-id conflict, auto-retrying event once: ${e}`),this.clearActiveEventIdleTimer(),this.clearActiveEventHardTimer(),this.activeEvent=null,this.stopComposing(),this.deliverInboundEvent(t),!0}recoverMalformedToolCall(e){if(this.stopped||!this.activeEvent||this.isEventAnswered()||!this.claudeCliSessionId||!this.claudeSessionCwd)return"none";const t=W(this.claudeCliSessionId,this.claudeSessionCwd,this.activeEvent.jsonlBaseOffset);if(!t||t.stopReason!=="stop_sequence"||t.text.trim()!==ti)return"none";if(this.malformedToolRetriedEventIds.has(e))return o.warn("claude-adapter",`Malformed tool-call retry exhausted for ${e}, sending clean fallback`),"exhausted";this.malformedToolRetriedEventIds.add(e);const i={...this.activeEvent.rawEvent,content:ii};return o.warn("claude-adapter",`Detected malformed tool-call, silently retrying event once: ${e}`),this.clearActiveEventIdleTimer(),this.clearActiveEventHardTimer(),this.activeEvent=null,this.stopComposing(),this.deliverInboundEvent(i),"retried"}async handleInternalInvoke(e,t,i,s){if(e==="mcp_stdio_log")return o.info("claude-mcp-stdio",String(t.message??"")),{ok:!0};if(e==="event_tool_call"){const n=String(t.tool_name??""),r=xt(n,{...t.arguments??{}}),a=this.getEventToolHandle();if(this.activeEvent){const c=this.activeEvent,u=String(r.event_id??"").trim();u===""?r.event_id=c.eventId:(n==="grix_reply"||n==="grix_complete")&&u!==c.eventId&&(o.warn("claude-adapter",`${n}: event_id mismatch, correcting: supplied=${u}, active=${c.eventId}`),r.event_id=c.eventId);const h=String(r.session_id??"").trim();if(n==="grix_reply"?(h&&h!==c.sessionId&&o.warn("claude-adapter",`${n}: session_id mismatch, correcting: supplied=${h}, active=${c.sessionId}`),r.session_id=c.sessionId):h===""&&(r.session_id=c.sessionId),n==="grix_reply"&&String(r.quoted_message_id??"").trim()===""){const p=String(c.rawEvent?.msg_id??"").trim();p&&(r.quoted_message_id=p)}n==="grix_reply"&&o.info("claude-adapter",`grix_reply quote: rawEvent.msg_id=${String(c.rawEvent?.msg_id??"")||"(none)"} -> quoted_message_id=${String(r.quoted_message_id??"")||"(none)"}`)}else if(this.lastClearedEvent&&n==="grix_reply"){const c=this.lastClearedEvent;Date.now()-c.ts<ui&&(String(r.session_id??"").trim()===""&&(r.session_id=c.sessionId),r.event_id="",o.info("claude-adapter",`Late grix_reply fallback: sending direct message for cleared event ${c.eventId} (cleared ${(Date.now()-c.ts)/1e3}s ago)`))}!this.activeEvent&&n==="grix_reply"&&(this.selfDrivenReplied=!0),n==="grix_reply"&&String(r.event_id??"").trim()!==""&&this.completedEventIds.has(String(r.event_id??"").trim())&&(o.info("claude-adapter",`Late grix_reply fallback: sending direct message for completed event ${String(r.event_id??"").trim()}`),r.event_id="");const l=$t(n,r);if(!l.valid)throw new Error(`\u53C2\u6570\u6821\u9A8C\u5931\u8D25: ${l.error}`);if(a.status!=="ready")throw new Error(`\u8FDE\u63A5\u4E0D\u53EF\u7528: \u5F53\u524D\u72B6\u6001\u4E3A ${a.status}`);if(Dt(n)){const c=this.activeEvent;c&&(c.toolCallInFlight=!0);try{if(i?.aborted)throw new Error("invoke aborted by timeout");const u=Re(a,n,r);if(u.isError)throw new Error(u.content[0]?.text??"event tool failed");if(i?.aborted)throw new Error("invoke aborted by timeout after send");return this.postProcessEventToolCall(n,r),JSON.parse(u.content[0]?.text??"null")}finally{if(c&&(c.toolCallInFlight=!1,c.pendingStopHook&&this.activeEvent===c)){const u=c.pendingStopHook;c.pendingStopHook=void 0,this.finalizeActiveEvent(u)}}}throw new Error(`\u672A\u77E5\u4E8B\u4EF6\u5DE5\u5177: ${n}`)}return this.bridgeCallbacks.agentInvoke(e,t,s)}getEventToolHandle(){const e=this;return{status:"ready",getStatusSnapshot:()=>({status:"ready",connectedAt:Date.now(),reconnectAttempts:0}),sendEventAck:t=>{e.bridgeCallbacks.sendEventAck(t.event_id,t.session_id??"")},sendStreamChunk:t=>{e.bridgeCallbacks.sendStreamChunk(t.event_id??"",t.session_id,t.delta_content??"",Number(t.chunk_seq??0)||1,t.is_finish===!0,t.client_msg_id,t.quoted_message_id)},sendMsg:t=>{if(typeof e.bridgeCallbacks.sendDirectMessage!="function"){o.warn("claude-adapter","sendDirectMessage callback not provided, dropping direct message");return}e.bridgeCallbacks.sendDirectMessage({sessionId:t.session_id,clientMsgId:t.client_msg_id,content:t.content,quotedMessageId:t.quoted_message_id})},sendEventResult:t=>{e.bridgeCallbacks.sendEventResult(t.event_id,t.status,t.msg,t.code)},sendSessionActivitySet:t=>{}}}postProcessEventToolCall(e,t){const i=String(t.event_id??"").trim();if(!i||this.activeEvent?.eventId!==i){o.warn("claude-adapter",`postProcessEventToolCall: event_id mismatch (tool=${e}, eventId=${i}, activeEventId=${this.activeEvent?.eventId??"none"})`);return}if(e==="grix_complete"){this.completedEventIds.set(i,Date.now()),this.clearActiveEvent();return}if(e==="grix_reply"){const s=!this.activeEvent.replied;this.activeEvent.replied=!0;const n=String(t.text??"");n.length>0&&(this.activeEvent.lastReplyText=n),this.markActiveEventActivity(i,String(t.session_id??"").trim()||void 0),s&&(this.activeEvent.repliedAt=Date.now(),this.startPostReplyDeadline(i),this.startPostReplyJsonlWatcher(i))}}resolveSessionRuntime(){if(!this.runtimeResolver)return{};try{return this.runtimeResolver(this.sessionId)??{}}catch(e){throw new Error(`resolve session runtime failed: ${e instanceof Error?e.message:String(e)}`)}}async validatePluginDir(e){const t=String(e??"").trim();if(!t)return;let i;try{i=await Ie(t)}catch{throw new Error(`pluginDir is not accessible: ${t}`)}if(!i.isDirectory())throw new Error(`pluginDir is not a directory: ${t}`);const s=f(t,".mcp.json");try{(await Ie(s)).isFile()&&(await Ae(s),o.info("claude-adapter",`Removed conflicting .mcp.json from pluginDir: ${s}`))}catch{}}async ensureWorkspaceTrust(e){const t=f(k(),".claude.json");let i=e;try{i=we(e)}catch{}const s=i===e?[e]:[e,i];try{const n=await T(t,"utf8"),r=JSON.parse(n.replace(/^\uFEFF/,""));r.projects||(r.projects={});let a=!1;for(const l of s)r.projects[l]?.hasTrustDialogAccepted!==!0&&(r.projects[l]||(r.projects[l]={}),r.projects[l].hasTrustDialogAccepted=!0,a=!0);if(!a)return;await D(t,JSON.stringify(r),"utf8"),o.info("claude-adapter",`Pre-trusted workspace: ${s.join(", ")}`)}catch(n){o.warn("claude-adapter",`Failed to pre-trust workspace ${e}: ${n}`)}}async ensureSkipDangerousPermissionPrompt(){const e=f(k(),".claude","settings.json");try{let t={};try{t=JSON.parse(await T(e,"utf8"))}catch{}if(t.skipDangerousModePermissionPrompt===!0)return;t.skipDangerousModePermissionPrompt=!0,await L(f(k(),".claude"),{recursive:!0}),await D(e,JSON.stringify(t,null,2),"utf8"),o.info("claude-adapter","Set skipDangerousModePermissionPrompt=true in user settings to skip BypassPermissions dialog")}catch(t){o.warn("claude-adapter",`Failed to set skipDangerousModePermissionPrompt: ${t}`)}}async isChannelGateClosed(){if(this.channelGateClosed)return!0;try{const e=await T(f(k(),".claude.json"),"utf8"),i=JSON.parse(e).cachedGrowthBookFeatures;return!i||Object.keys(i).length===0?!1:i.tengu_harbor!==!0}catch{return!1}}async ensureClaudeOnboardingFlags(e){const t=f(k(),".claude.json");try{let i;try{const r=await T(t,"utf8");i=JSON.parse(r)}catch{i={}}let s=!1;i.hasCompletedOnboarding||(i.hasCompletedOnboarding=!0,i.lastOnboardingVersion||(i.lastOnboardingVersion="2.1.31"),s=!0),i.projects||(i.projects={});const n=i.projects;if(n[e]||(n[e]={}),n[e].hasCompletedProjectOnboarding||(n[e].hasCompletedProjectOnboarding=!0,s=!0),this.config.env?.ANTHROPIC_API_KEY===Oe){const r=Oe.slice(-20),a=i.customApiKeyResponses??{},l=a.approved??[],c=a.rejected??[];(!l.includes(r)||c.includes(r))&&(i.customApiKeyResponses={...a,approved:[...l.filter(u=>u!==r),r],rejected:c.filter(u=>u!==r)},s=!0)}if(!s)return;await D(t,JSON.stringify(i,null,2),"utf8"),o.info("claude-adapter",`Marked Claude onboarding complete: ${e}`)}catch(i){o.warn("claude-adapter",`Failed to mark Claude onboarding complete: ${i}`)}}async injectStatusLineSettings(e){try{const t=this.resolveProjectRoot(),i=f(t,"dist","scripts","status-line-forwarder.js"),s=f(e,".claude"),n=f(s,"settings.json");await L(s,{recursive:!0});let r={};try{r=JSON.parse(await T(n,"utf8"))}catch{}const l={type:"command",command:`node "${i.replace(/\\/g,"/")}"`,refreshInterval:10};r.statusLine=l;const c=`${JSON.stringify(r,null,2)}
18
+ `;await D(n,c,"utf8"),o.info("claude-adapter",`Injected statusLine settings: ${n}`)}catch(t){o.warn("claude-adapter",`Failed to inject statusLine settings: ${t instanceof Error?t.message:t}`)}}async ensureUserMcpServer(e,t,i){const s=this.resolveServerEntryPath(t),n=process.execPath,r=[s],a=f(k(),".claude.json");let l=null;try{const p=await T(a,"utf8");l=JSON.parse(p)?.mcpServers?.[M]??null}catch{}if(l&&String(l.type||"stdio").trim()==="stdio"&&String(l.command??"").trim()===n&&Array.isArray(l.args)&&l.args.length===r.length&&l.args.every((p,m)=>p===r[m]))return;o.info("claude-adapter",`Registering user-scoped MCP server: ${M} -> ${n} ${r.join(" ")}`);try{re(`${e} mcp remove -s user ${M}`,{encoding:"utf8",timeout:1e4,env:i,stdio:"pipe",windowsHide:!0})}catch{}const c=["mcp","add","--scope","user",M,"--",n,...r],u=process.platform==="win32"?'"':"'",h=re(`${e} ${c.map(p=>`${u}${p}${u}`).join(" ")}`,{encoding:"utf8",timeout:1e4,env:i,stdio:"pipe",windowsHide:!0});o.info("claude-adapter",`MCP server registered: ${h.trim()||"ok"}`)}resolveServerEntryPath(e){const t=f(e,"server","main.js");try{if(B(t))return t}catch{}const i=f(e,"dist","index.js");try{if(B(i))return i}catch{}throw new Error(`Cannot find grix-claude server entry in pluginDir: ${e}`)}clearActiveEvent(){const e=this.activeEvent;this.clearActiveEventIdleTimer(),this.clearActiveEventHardTimer(),this.clearActiveEventPostReplyTimer(),this.clearActiveEventPostReplyWatcher(),this.clearRescueSettleTimer(),e&&(this.snapshotAuditBoundary(e),this.sessionIdConflictRetriedEventIds.delete(e.eventId),this.malformedToolRetriedEventIds.delete(e.eventId),this.lostInjectionRetriedEventIds.delete(e.eventId),this.completedEventIds.set(e.eventId,Date.now()),this.lastClearedEvent={eventId:e.eventId,sessionId:e.sessionId,ts:Date.now()},this.emit(`reply:${e.sessionId}`,{status:"completed"}),this.emit("eventDone",e.eventId)),this.activeEvent=null,this.stopComposing(),this.resetSelfDrivenTextBaseline()}startComposing(e,t){}stopComposing(){}clearComposingTimer(){}resolveHookSignalsPath(){const e=y(),t=this.claudeCliSessionId||this.sessionId;return f(e.dataDir,`hook-signals-${t}.json`)}startActivityTracking(){this.activityManager&&this.activityManager.stop();const e=this.resolveHookSignalsPath(),t=y().hookSignalsLogPath,i=new kt(e,t);o.info("claude-adapter",`Activity tracking started: watching ${e}`),this.activityManager=new bt({hookSignalStore:i,onActivity:s=>this.onHookActivity(s),onTranscriptPath:s=>this.onHookTranscriptPath(s),onStop:()=>this.onClaudeTurnEnd("Stop"),onStopFailure:()=>this.onClaudeTurnEnd("StopFailure"),onCompactStart:()=>this.beginCompaction(),onCompactResult:()=>this.finishCompaction("post-compact-hook"),onPermissionRequest:(s,n)=>this.handlePermissionHookEvent(s,n),onUserPromptSubmit:s=>this.onUserPromptSubmit(s)}),this.activityManager.start()}onHookTranscriptPath(e){this.claudeCliSessionId&&jt(this.claudeCliSessionId,e)}onUserPromptSubmit(e){const t=this.activeEvent;!t||!e||e!==t.eventId||t.promptSubmittedAt!==void 0||(t.promptSubmittedAt=Date.now(),o.info("claude-adapter",`Prompt submitted for ${t.eventId}${t.channelPushedAt?` (${t.promptSubmittedAt-t.channelPushedAt}ms after push)`:""}`))}isChannelEventPrePush(e){return!e.singleTurnFallback&&e.channelPushedAt===void 0&&e.jsonlBaseOffset===void 0}isStopBeforePromptSubmit(e){return e.singleTurnFallback?!1:e.channelPushedAt===void 0?e.jsonlBaseOffset===void 0:e.promptSubmittedAt!==void 0?!1:Date.now()-e.channelPushedAt<fi}onClaudeTurnEnd(e){if(o.info("claude-adapter",`Hook activity: ${e}`),!this.activeEvent){this.stopHookBarrierSessionId&&(o.info("claude-adapter",`Late Stop hook after end_turn finalize \u2014 releasing barrier (session=${this.stopHookBarrierSessionId})`),this.clearStopHookBarrier()),this.deliverSelfDrivenText(e);return}if(this.activeEvent.toolCallInFlight){o.info("claude-adapter",`Stop hook deferred: toolCallInFlight for ${this.activeEvent.eventId}`),this.activeEvent.pendingStopHook=e;return}if(!this.isEventAnswered()&&this.isStopBeforePromptSubmit(this.activeEvent)){o.warn("claude-adapter",`${e} hook ignored for ${this.activeEvent.eventId}: arrived before this event's prompt was submitted (${Date.now()-(this.activeEvent.channelPushedAt??0)}ms after push) \u2014 startup/resume turn tail`);return}if(this.activeEvent.singleTurnFallback&&!this.isEventAnswered()){o.info("claude-adapter",`${e} hook ignored for ${this.activeEvent.eventId}: single-turn fallback owns finalization`);return}this.finalizeActiveEvent(e)}resolveIncrementalTextFromOffset(e,t){if(!this.isChannelEventPrePush(t)){if(t.textForwardOffset!==void 0)return t.textForwardOffset;if(t.jsonlBaseOffset!==void 0)return t.jsonlBaseOffset;if(t.singleTurnFallback){if(!this.claudeCliSessionId||!this.claudeSessionCwd)return;const i=O(this.claudeCliSessionId,this.claudeSessionCwd);return S(i)?0:void 0}}}forwardIncrementalText(e){const t=this.activeEvent;if(!t||t.eventId!==e||this.isChannelEventPrePush(t)||t.replied||!this.claudeCliSessionId||!this.claudeSessionCwd)return;const i=this.resolveIncrementalTextFromOffset(e,t);if(i===void 0)return;const{segments:s,newOffset:n,apiError:r,apiErrorText:a}=De(this.claudeCliSessionId,this.claudeSessionCwd,i);if(t.textForwardOffset=n,r&&(a&&(t.apiErrorText=a),t.apiFormatError||(t.apiFormatError=!0,o.error("claude-adapter",`API error entry detected in JSONL for ${e} (isApiErrorMessage), not forwarding its text as reply`))),s.length===0)return;const l=t.sessionId;for(const c of s){const u=c.trim();if(!u)continue;const h=`txt_${e}_${this.textForwardSeq++}`;this.bridgeCallbacks.sendStreamChunk(e,l,u,1,!1,h),this.bridgeCallbacks.sendStreamChunk(e,l,"",2,!0,h),t.textForwarded||(t.textForwarded=!0,t.repliedAt===void 0&&(t.repliedAt=Date.now()),this.startPostReplyDeadline(e),this.startPostReplyJsonlWatcher(e))}this.markActiveEventActivity(e,l),o.info("claude-adapter",`Forwarded ${s.length} plain-text segment(s) for ${e}`)}flushForwardedTail(e){this.activeEvent?.eventId===e&&this.activeEvent.textForwarded&&this.forwardIncrementalText(e)}isEventAnswered(e=this.activeEvent){return!!(e&&(e.replied||e.textForwarded))}attemptRescueFromJsonl(e,t){if(!this.claudeCliSessionId||!this.claudeSessionCwd)return o.info("claude-adapter",`Rescue skipped: no claudeCliSessionId or claudeSessionCwd for ${e}`),"none";const i=this.activeEvent?.jsonlBaseOffset,s=W(this.claudeCliSessionId,this.claudeSessionCwd,i);if(s?.apiError)return o.error("claude-adapter",`Rescue aborted for ${e}: turn ended with a CLI API error entry (isApiErrorMessage)`),this.activeEvent?.eventId===e&&(this.activeEvent.apiFormatError=!0,s.apiErrorText&&(this.activeEvent.apiErrorText=s.apiErrorText)),"none";if(s?.metaOnly)return i===void 0?(o.warn("claude-adapter",`Rescue for ${e}: only CLI meta text found but jsonlBaseOffset is unset \u2014 cannot attribute it to this turn, treating as unanswered`),"none"):(o.info("claude-adapter",`Rescue skipped for ${e}: this turn wrote only CLI meta text, finalizing silently`),"silent");const n=s?.text||null;if(!n)return o.info("claude-adapter",`Rescue failed: no assistant text found in JSONL for ${e}`),"none";const r=`rescue_${e}_${Date.now()}`;return this.bridgeCallbacks.sendStreamChunk(e,t,n,1,!1,r),this.bridgeCallbacks.sendStreamChunk(e,t,"",2,!0,r),o.info("claude-adapter",`Rescue succeeded for event ${e}: sent ${n.length} chars from JSONL`),"sent"}warnUnsentFinalIfAny(e){if(!this.claudeCliSessionId||!this.claudeSessionCwd)return;const t=this.activeEvent?.jsonlBaseOffset,i=W(this.claudeCliSessionId,this.claudeSessionCwd,t);if(!i||i.stopReason!=="end_turn")return;const s=i.text?.trim()??"";if(!s)return;const n=(this.activeEvent?.lastReplyText??"").trim();n&&(s===n||n.includes(s))||o.warn("claude-adapter",`Unsent final end_turn text for ${e} (${s.length} chars) not delivered \u2014 \u5DF2\u5E94\u7B54\u8FC7\uFF0C\u6309\u7B56\u7565\u4E0D\u8865\u53D1\uFF08\u7591\u4F3C\u56DE\u5408\u6536\u5C3E\u81EA\u8FF0\uFF1B\u82E5\u786E\u4E3A\u6F0F\u53D1\u7ED3\u8BBA\u53EF\u636E\u6B64\u6392\u67E5\uFF09`)}finalizeActiveEvent(e){if(!this.activeEvent)return;const t=this.activeEvent.eventId,i=this.activeEvent.sessionId;if(this.isEventAnswered()&&this.activeEvent.jsonlBaseOffset!==void 0&&this.claudeCliSessionId&&this.claudeSessionCwd){const s=W(this.claudeCliSessionId,this.claudeSessionCwd,this.activeEvent.jsonlBaseOffset);if(s?.apiError)o.warn("claude-adapter",`Stop hook for ${t}: turn ended with a CLI API error entry, finalizing without waiting for end_turn`);else if(!s||s.stopReason!=="end_turn")if($e(this.claudeCliSessionId,this.claudeSessionCwd,this.activeEvent.jsonlBaseOffset))o.info("claude-adapter",`Stop hook: terminal tool_result detected for ${t} (grix_reply as final action, no end_turn expected), finalizing as responded`);else{o.info("claude-adapter",`Stop hook suppressed for ${t}: no end_turn in JSONL after offset=${this.activeEvent.jsonlBaseOffset} \u2014 likely resume-drain hook, waiting for JSONL watcher`),this.markActiveEventActivity(t,i);return}}if(this.flushForwardedTail(t),!this.activeEvent.apiFormatError){if(this.activeEvent.replied||this.activeEvent.textForwarded){this.activeEvent.replied&&!this.activeEvent.textForwarded&&this.warnUnsentFinalIfAny(t),this.finishTurnEnd(t,i,e,!0,`replied=${this.activeEvent.replied}, textForwarded=${this.activeEvent.textForwarded}`);return}if(this.activeEvent.rescueSettleInFlight){o.info("claude-adapter",`${e} ignored for ${t}: rescue retry already in flight`);return}const s=this.recoverMalformedToolCall(t);if(s==="retried")return;if(s==="exhausted"){this.bridgeCallbacks.sendStreamChunk(t,i,si,1,!1,`mtc_${t}`),this.bridgeCallbacks.sendStreamChunk(t,i,"",2,!0,`mtc_${t}`),this.finishTurnEnd(t,i,e,!0,"malformed-toolcall-fallback");return}const n=this.attemptRescueFromJsonl(t,i);if(n!=="none"){this.finishTurnEnd(t,i,e,!0,n==="silent"?"no-response-requested":"rescued");return}if(this.activeEvent?.apiFormatError){this.finishTurnEnd(t,i,e,!1,"apiFormatError (detected during rescue)");return}this.beginRescueSettleWait(t,i,e);return}this.finishTurnEnd(t,i,e,!1,"apiFormatError")}beginRescueSettleWait(e,t,i){if(!this.activeEvent||this.activeEvent.eventId!==e)return;this.activeEvent.rescueSettleInFlight=!0;const s=Math.max(1,Math.ceil(Je/We));let n=0;const r=()=>{if(this.rescueSettleTimer=null,this.stopped)return;const a=this.activeEvent;if(!a||a.eventId!==e)return;if(n++,a.replied||a.textForwarded){this.finishTurnEnd(e,t,i,!0,`replied=${a.replied}, textForwarded=${a.textForwarded} (settled during flush wait)`);return}if(a.apiFormatError){this.finishTurnEnd(e,t,i,!1,"apiFormatError (settled during flush wait)");return}const l=this.attemptRescueFromJsonl(e,t);if(l!=="none"){this.finishTurnEnd(e,t,i,!0,l==="silent"?"no-response-requested-after-settle":"rescued-after-settle");return}if(a.apiFormatError){this.finishTurnEnd(e,t,i,!1,"apiFormatError (detected during rescue after settle)");return}if(i==="Stop"&&this.isToolOnlyTurnConcluded(a)){this.finishTurnEnd(e,t,i,!0,`tool-only-turn (toolCalls=${a.toolActivityCount??0})`);return}if(n>=s){this.finishTurnEnd(e,t,i,!1,`rescue-failed-after-settle (waited ~${Math.round(Je/1e3)}s, no transcript output for this turn)`);return}this.markActiveEventActivity(e,t),this.rescueSettleTimer=setTimeout(r,We)};this.rescueSettleTimer=setTimeout(r,Xt)}isToolOnlyTurnConcluded(e){return!e.toolActivityCount||e.jsonlBaseOffset===void 0||!this.claudeCliSessionId||!this.claudeSessionCwd?!1:ce(this.claudeCliSessionId,this.claudeSessionCwd,e.jsonlBaseOffset).lastStopReason==="end_turn"}clearRescueSettleTimer(){this.rescueSettleTimer&&(clearTimeout(this.rescueSettleTimer),this.rescueSettleTimer=null)}finishTurnEnd(e,t,i,s,n){if(s)o.info("claude-adapter",`Stop hook received, finalizing event ${e} as responded (${n})`),this.bridgeCallbacks.sendEventResult(e,"responded");else{o.warn("claude-adapter",`Active event not confirmed when ${i}: ${e} (${n}), sending failed result`);const r=this.activeEvent?.apiFormatError?de(this.activeEvent.apiErrorText):"Claude exited before completing its reply. Please try again.";this.bridgeCallbacks.sendStreamChunk(e,t,r,1,!1,`err_${e}`),this.bridgeCallbacks.sendStreamChunk(e,t,"",2,!0,`err_${e}`),this.bridgeCallbacks.sendEventResult(e,"failed",r,"agent_stop_failure",!0)}this.clearActiveEvent(),this.armStopHookBarrier(t)}onHookActivity(e){const t=this.activeEvent?.sessionId;if(o.info("claude-adapter",`Hook activity: tool=${e?.tool_name??"(clear)"} session=${t??"(none)"}`),e&&this.activeEvent&&!this.isChannelEventPrePush(this.activeEvent)&&(this.activeEvent.toolActivityCount=(this.activeEvent.toolActivityCount??0)+1,this.markActiveEventActivity(this.activeEvent.eventId,this.activeEvent.sessionId),this.forwardIncrementalText(this.activeEvent.eventId),(this.activeEvent.replied||this.activeEvent.textForwarded)&&this.startPostReplyDeadline(this.activeEvent.eventId)),e&&!this.activeEvent&&!this.compacting&&this.noteSelfDrivenActivity(e),!!t)if(e){this.startComposing(t,e);const i=this.activeEvent;if(i&&!this.isChannelEventPrePush(i)){const s=e.event_name,n=e.tool_name,r=e.tool_input??"",a=n?Ot(n):!1;if(n==="ExitPlanMode"&&s==="PreToolUse")o.info("claude-adapter","ExitPlanMode detected; waiting for user decision via permission card");else if(n==="AskUserQuestion"&&s==="PreToolUse")this.handleAskUserQuestion(i,r);else if(s==="PreToolUse"&&n)a||this.bridgeCallbacks.sendToolUse(i.eventId,i.sessionId,n,r);else if((s==="PostToolUse"||s==="PostToolUseFailure")&&n){if(n==="AskUserQuestion")return;!a&&s==="PostToolUseFailure"&&this.bridgeCallbacks.sendToolResult(i.eventId,i.sessionId,n,"(failed)")}}}else this.startComposing(t)}handlePermissionHookEvent(e,t){if(!this.activeEvent){o.warn("claude-adapter","PermissionRequest without active event, ignoring");return}if(e==="AskUserQuestion"){o.info("claude-adapter","Skip permission card for AskUserQuestion; handled by agent_question card flow");return}const i=this.activeEvent,s=y();new z(s.permissionRequestsDir).listPending().then(r=>{const a=r.length>0?r[r.length-1]:null;if(!a){o.warn("claude-adapter","No pending permission request found in store");return}const l=a.request_id;this.pendingPermissions.set(l,{eventId:i.eventId,sessionId:i.sessionId});const c=typeof t=="string"?t:"";if(e==="ExitPlanMode"){const p=Gt(c);p&&this.bridgeCallbacks.sendReply(i.eventId,i.sessionId,p)}const u=e==="ExitPlanMode"?"":c.slice(0,100),h=u?`${e}: ${u}`:e;this.bridgeCallbacks.sendPermissionCard({eventId:i.eventId,sessionId:i.sessionId,approvalId:l,toolName:e,toolTitle:h}),o.info("claude-adapter",`Sent permission card: approvalId=${l} tool=${e}`)}).catch(r=>{o.warn("claude-adapter",`Failed to send permission card: ${r}`)})}async awaitPendingQuestionRequest(e,t){const i=Date.now()+Ne;for(;;){const s=await e.listPending(),n=Date.now(),r=[...s].reverse().filter(l=>String(l.session_id??"").trim()===this.claudeCliSessionId&&n-Number(l.created_at??0)<=Fe),a=r.find(l=>String(l.event_id??"").trim()===t)??r.find(l=>String(l.event_id??"").trim()==="")??r[0]??null;if(a||Date.now()>=i)return a;await new Promise(l=>setTimeout(l,Kt))}}handleAskUserQuestion(e,t){const i=xe(t);if(!i){o.warn("claude-adapter","Failed to parse AskUserQuestion input, skipping agent_question card");return}const s=y(),n=new ke(s.questionRequestsDir);this.awaitPendingQuestionRequest(n,e.eventId).then(async r=>{const a=r?.request_id??`fallback-${Date.now()}`;r||o.warn("claude-adapter",`No pending question waiter after ${Ne}ms; sending card with ${a}. Submitting it will report not_pending and fall back to a plain message.`),r&&String(r.event_id??"").trim()===""&&await n.updateRequest(r.request_id,{event_id:e.eventId}),this.pendingQuestion.set(a,{eventId:e.eventId,sessionId:e.sessionId}),e.awaitingUserQuestion=!0;const c=(Number(r?.created_at??0)>0?Number(r?.created_at):Date.now())+Me;this.bridgeCallbacks.sendAgentQuestionCard(e.eventId,e.sessionId,{request_id:a,mode:"form",questions:i,expires_at:c}),o.info("claude-adapter",`Sent agent_question card: request_id=${a} questions=${i.length}`)}).catch(()=>{o.warn("claude-adapter","Failed to list pending questions from store")})}parseAskUserQuestions(e){return xe(e)}parseAskUserQuestionInput(e){let t;try{t=JSON.parse(e)}catch{return null}const i=[],s=t.questions;if(!Array.isArray(s)||s.length===0)return null;for(let n=0;n<s.length;n++){const r=s[n];if(!r||typeof r!="object")continue;const a=String(r.header??r.question??`Question ${n+1}`),l=String(r.prompt??""),c=Array.isArray(r.options)?r.options:void 0,u=r.multiSelect===!0;i.push({header:a,prompt:l,...c&&c.length>0?{options:c}:{},...u?{multi_select:!0}:{}})}return i.length>0?{questions:i}:null}pruneCompletedEvents(){const e=Date.now()-Vt;for(const[t,i]of this.completedEventIds.entries())i<e&&this.completedEventIds.delete(t)}markActiveEventActivity(e,t){const i=this.activeEvent;i&&(e&&i.eventId!==e||t&&i.sessionId!==t||(this.resetActiveEventIdleTimer(i.eventId),this.isEventAnswered(i)||this.resetActiveEventHardTimer(i.eventId)))}clearActiveEventIdleTimer(){this.activeEventIdleTimer&&(clearTimeout(this.activeEventIdleTimer),this.activeEventIdleTimer=null)}clearActiveEventHardTimer(){this.activeEventHardTimer&&(clearTimeout(this.activeEventHardTimer),this.activeEventHardTimer=null)}shouldExtendByLiveness(e){const t=this.activeEvent;if(!t||t.eventId!==e||!this.claudeCliSessionId||!this.claudeSessionCwd)return!1;const i=this.singleTurnFallbackChild,s=!!i&&i.exitCode===null&&!i.killed;if(!this.claudeProcess&&!this.claudePty&&!s)return!1;if(this.lastPtyOutputAt>0&&Date.now()-this.lastPtyOutputAt<Ke)return t.livenessExtendStartAt=Date.now(),!0;const n=ce(this.claudeCliSessionId,this.claudeSessionCwd,t.jsonlBaseOffset);if(n.freshMs===null||!(t.jsonlBaseOffset===void 0?n.freshMs<ue:n.lastStopReason!==null?n.lastStopReason!=="end_turn":n.freshMs<ue))return!1;if(n.freshMs<ue)t.livenessExtendStartAt=Date.now();else{const a=t.livenessExtendStartAt??Date.now();if(t.livenessExtendStartAt===void 0&&(t.livenessExtendStartAt=a),Date.now()-a>Ye)return o.warn("claude-adapter",`Liveness extension budget exhausted for ${e} (no JSONL writes for ${Math.round((Date.now()-a)/6e4)}min), allowing close`),!1}return o.info("claude-adapter",`Liveness check: turn in progress for ${e} (lastStopReason=${n.lastStopReason??"none"}, freshMs=${n.freshMs}), extending`),!0}captureEventJsonlBaseOffset(e,t){this.captureAuditTranscriptStart(e);const i=this.activeEvent;if(!(!i||i.eventId!==e)&&!(!t?.force&&i.jsonlBaseOffset!==void 0)&&!(!this.claudeCliSessionId||!this.claudeSessionCwd))try{const s=O(this.claudeCliSessionId,this.claudeSessionCwd);if(i.auditTranscriptPath===s&&i.auditTranscriptStartOffset!==void 0){i.jsonlBaseOffset=i.auditTranscriptStartOffset;return}i.jsonlBaseOffset=S(s)?I(s).size:0}catch{i.jsonlBaseOffset=0}}captureSingleTurnFallbackJsonlBaseOffset(e){this.captureAuditTranscriptStart(e);const t=this.activeEvent;if(!(!t||t.eventId!==e||t.jsonlBaseOffset!==void 0)&&!(!this.claudeCliSessionId||!this.claudeSessionCwd))try{const i=O(this.claudeCliSessionId,this.claudeSessionCwd);if(!S(i))return;if(t.auditTranscriptPath===i&&t.auditTranscriptStartOffset!==void 0){t.jsonlBaseOffset=t.auditTranscriptStartOffset;return}t.jsonlBaseOffset=I(i).size}catch{}}captureAuditTranscriptStart(e){const t=this.activeEvent;if(!t||t.eventId!==e||t.rawEvent?.audit?.enabled!==!0||t.auditTranscriptStartOffset!==void 0||!this.claudeCliSessionId)return;const i=this.claudeSessionCwd||process.cwd(),s=O(this.claudeCliSessionId,i);t.auditTranscriptPath=s,t.auditClaudeSessionId=this.claudeCliSessionId;try{t.auditTranscriptStartOffset=S(s)?I(s).size:0}catch{t.auditTranscriptStartOffset=0}const n=this.auditEvidencePaths;n&&(t.auditOtelLogsStartOffset=Q(n.logsPath),t.auditOtelTracesStartOffset=Q(n.tracesPath),n.rawProviderBody&&(t.auditRawBodyFilesBefore=Ge(n.rawBodyDir)))}snapshotAuditBoundary(e){if(e.rawEvent?.audit?.enabled!==!0)return;if(e.auditTranscriptStartOffset===void 0||!e.auditTranscriptPath||!e.auditClaudeSessionId){this.completedAuditBoundaries.set(e.eventId,Object.freeze({adapterType:this.type}));return}const t=this.claudeSessionCwd?O(e.auditClaudeSessionId,this.claudeSessionCwd):e.auditTranscriptPath,i=S(t)?t:e.auditTranscriptPath;let s=e.auditTranscriptStartOffset;try{s=S(i)?I(i).size:e.auditTranscriptStartOffset}catch{}const n=St(i),r=this.auditEvidencePaths,a=new Set(e.auditRawBodyFilesBefore??[]),l=r?.rawProviderBody?Ge(r.rawBodyDir).filter(u=>!a.has(u)):[],c=Object.freeze({adapterType:this.type,providerSessionId:e.auditClaudeSessionId,transcriptPath:i,transcriptStartOffset:e.auditTranscriptStartOffset,transcriptEndOffset:s,...e.auditSingleTurnFallback?{singleTurnFallback:!0}:{},subagentRoot:f(n,e.auditClaudeSessionId,"subagents"),...r?{otelLogsPath:r.logsPath,otelLogsStartOffset:e.auditOtelLogsStartOffset??0,otelLogsEndOffset:Q(r.logsPath),otelTracesPath:r.tracesPath,otelTracesStartOffset:e.auditOtelTracesStartOffset??0,otelTracesEndOffset:Q(r.tracesPath),rawApiBodyDirectory:r.rawBodyDir,rawApiBodyPaths:l}:{}});this.completedAuditBoundaries.set(e.eventId,c)}noteSelfDrivenActivity(e){!this.selfDrivenActive&&this.lastClearedEvent&&Date.now()-this.lastClearedEvent.ts<Qe||(this.selfDrivenLastSignalAt=Date.now(),e&&(this.selfDrivenLabel=ci(e)),!this.selfDrivenActive&&(this.selfDrivenActive=!0,o.info("claude-adapter",`Self-driven activity detected for session ${this.sessionId} (no active event), debouncing before reporting working state`),this.selfDrivenReportTimer=setTimeout(()=>{this.selfDrivenReportTimer=null,!(!this.selfDrivenActive||this.stopped||this.activeEvent)&&(this.selfDrivenReported=!0,o.info("claude-adapter",`Self-driven working state confirmed for session ${this.sessionId} (sustained >${Ve/1e3}s), reporting`),this.emit("sessionActivity",this.sessionId??"",!0,this.selfDrivenLabel??void 0))},Ve),this.selfDrivenReportTimer.unref(),this.selfDrivenSweepTimer=setInterval(()=>this.sweepSelfDriven(),ai),this.selfDrivenSweepTimer.unref()))}sweepSelfDriven(){if(!this.selfDrivenActive)return;if(this.stopped||this.activeEvent){this.stopSelfDriven();return}const e=Date.now()-this.selfDrivenLastSignalAt;if(this.claudeCliSessionId&&this.claudeSessionCwd){const t=ce(this.claudeCliSessionId,this.claudeSessionCwd),i=t.lastStopReason!==null&&t.lastStopReason!=="end_turn",s=t.freshMs!==null&&t.freshMs<Ye,n=Nt(this.claudeCliSessionId,this.claudeSessionCwd)>0;if((i||n)&&s){this.selfDrivenReported&&this.emit("sessionActivity",this.sessionId??"",!0,this.selfDrivenLabel??void 0);return}}if(e<ri){this.selfDrivenReported&&this.emit("sessionActivity",this.sessionId??"",!0,this.selfDrivenLabel??void 0);return}o.info("claude-adapter",`Self-driven activity ended for session ${this.sessionId} (quiet for ${Math.round(e/1e3)}s, no pending work)`),this.stopSelfDriven()}stopSelfDriven(){this.selfDrivenSweepTimer&&(clearInterval(this.selfDrivenSweepTimer),this.selfDrivenSweepTimer=null),this.selfDrivenReportTimer&&(clearTimeout(this.selfDrivenReportTimer),this.selfDrivenReportTimer=null),this.selfDrivenActive&&(this.selfDrivenActive=!1,this.selfDrivenLabel=null,this.selfDrivenReported&&(this.selfDrivenReported=!1,this.emit("sessionActivity",this.sessionId??"",!1)))}currentTranscriptSize(){if(!this.claudeCliSessionId||!this.claudeSessionCwd)return null;try{return I(O(this.claudeCliSessionId,this.claudeSessionCwd)).size}catch{return null}}resetSelfDrivenTextBaseline(){this.clearSelfDrivenDeliverTimer(),this.selfDrivenReplied=!1,this.selfDrivenTextOffset=this.currentTranscriptSize()}clearSelfDrivenDeliverTimer(){this.selfDrivenDeliverTimer&&(clearTimeout(this.selfDrivenDeliverTimer),this.selfDrivenDeliverTimer=null)}deliverSelfDrivenText(e){if(this.stopped||this.activeEvent)return;this.clearSelfDrivenDeliverTimer();const t=this.currentTranscriptSize();if(t===null)return;if(this.selfDrivenTextOffset===null){this.selfDrivenTextOffset=t;return}if(this.lastClearedEvent&&Date.now()-this.lastClearedEvent.ts<Qe){this.selfDrivenTextOffset=t;return}if(this.selfDrivenReplied){this.selfDrivenReplied=!1,this.selfDrivenTextOffset=t;return}const i=Date.now()+li,s=()=>{if(this.selfDrivenDeliverTimer=null,!(this.stopped||this.activeEvent)&&!this.flushSelfDrivenText(e)){if(Date.now()>=i){o.info("claude-adapter",`Self-driven ${e}: no new transcript text to deliver for session ${this.sessionId}`);return}this.selfDrivenDeliverTimer=setTimeout(s,oi),this.selfDrivenDeliverTimer.unref()}};s()}flushSelfDrivenText(e){if(!this.claudeCliSessionId||!this.claudeSessionCwd)return!1;const t=this.selfDrivenTextOffset??0,{segments:i,newOffset:s}=De(this.claudeCliSessionId,this.claudeSessionCwd,t);this.selfDrivenTextOffset=s;const n=i.map(a=>a.trim()).filter(a=>a.length>0);if(n.length===0)return!1;const r=this.sessionId;if(!r)return o.warn("claude-adapter",`Self-driven ${e}: ${n.length} text segment(s) ready but session id is unknown, dropping`),!0;for(const a of n)Re(this.getEventToolHandle(),"grix_reply",{session_id:r,text:a});return o.info("claude-adapter",`Self-driven ${e}: delivered ${n.length} text segment(s) to session ${r}`),!0}tryRecoverLostInjection(e){const t=this.activeEvent;if(this.stopped||!t||t.eventId!==e||this.isEventAnswered(t)||t.channelPushedAt===void 0||t.promptSubmittedAt!==void 0||this.compacting||this.stopHookBarrierSessionId===t.sessionId||this.lostInjectionRetriedEventIds.has(e))return!1;this.lostInjectionRetriedEventIds.add(e);const i=t.rawEvent;return o.warn("claude-adapter",`Lost injection for ${e}: pushed ${Date.now()-t.channelPushedAt}ms ago but prompt never submitted \u2014 respawning Claude and re-pushing the event once`),this.clearActiveEventIdleTimer(),this.clearActiveEventHardTimer(),this.activeEvent=null,this.stopComposing(),this.killClaudeProcess(Be),this.deliverInboundEvent(i),!0}resetActiveEventIdleTimer(e){this.clearActiveEventIdleTimer(),this.activeEventIdleTimer=setTimeout(()=>{if(!this.stopped&&this.activeEvent?.eventId===e){if(this.activeEvent?.toolCallInFlight){o.info("claude-adapter",`Idle timeout skipped: toolCallInFlight for ${e}, resetting timer`),this.resetActiveEventIdleTimer(e);return}if(this.activeEvent?.awaitingUserQuestion){o.info("claude-adapter",`Idle timeout skipped: awaitingUserQuestion for ${e}, resetting timer`),this.resetActiveEventIdleTimer(e);return}if(this.pendingPermissions.size>0){o.info("claude-adapter",`Idle timeout skipped: pendingPermissions=${this.pendingPermissions.size} for ${e}, resetting timer`),this.resetActiveEventIdleTimer(e);return}if(this.shouldExtendByLiveness(e)){this.resetActiveEventIdleTimer(e);return}this.tryRecoverLostInjection(e)||this.finalizeStuckActiveEvent(e,"idle")}},je)}finalizeStuckActiveEvent(e,t){const i=this.isEventAnswered(),s=i?"responded":"failed",n=t==="idle"?je:Ue,r=!i&&this.activeEvent?.apiFormatError?de(this.activeEvent.apiErrorText):void 0,a=i?void 0:r??(t==="idle"?`agent idle for ${n/1e3}s`:`agent exceeded max duration ${n/1e3}s`),l=i?void 0:t==="idle"?"agent_idle_timeout":"agent_hard_timeout",c=this.lostInjectionRetriedEventIds.has(e)?", after lost-injection retry":"";o.error("claude-adapter",`Active event ${t} timeout (${n/1e3}s): ${e}, answered=${i}${c}, sending ${s}`),this.flushForwardedTail(e),this.completedEventIds.set(e,Date.now());let u=!1;r&&this.activeEvent&&(this.bridgeCallbacks.sendStreamChunk(e,this.activeEvent.sessionId,r,1,!1,`err_${e}`),this.bridgeCallbacks.sendStreamChunk(e,this.activeEvent.sessionId,"",2,!0,`err_${e}`),u=!0),u?this.bridgeCallbacks.sendEventResult(e,s,a,l,!0):this.bridgeCallbacks.sendEventResult(e,s,a,l),this.clearActiveEvent(),s==="failed"&&this.emit("stuck")}resetActiveEventHardTimer(e){this.clearActiveEventHardTimer(),this.activeEventHardTimer=setTimeout(()=>{if(!this.stopped&&this.activeEvent?.eventId===e){if(this.shouldExtendByLiveness(e)){this.resetActiveEventHardTimer(e);return}this.finalizeStuckActiveEvent(e,"hard")}},Ue)}clearActiveEventPostReplyTimer(){this.activeEventPostReplyTimer&&(clearTimeout(this.activeEventPostReplyTimer),this.activeEventPostReplyTimer=null)}clearActiveEventPostReplyWatcher(){if(this.activeEventPostReplyWatcher){try{this.activeEventPostReplyWatcher.close()}catch{}this.activeEventPostReplyWatcher=null}this.activeEventPostReplyPoll&&(clearInterval(this.activeEventPostReplyPoll),this.activeEventPostReplyPoll=null)}startPostReplyJsonlWatcher(e){if(this.clearActiveEventPostReplyWatcher(),!this.claudeCliSessionId||!this.claudeSessionCwd)return;const t=O(this.claudeCliSessionId,this.claudeSessionCwd);if(!this.activeEvent?.sessionId)return;if(!S(t)){o.info("claude-adapter",`JSONL watcher skipped: file not yet available for ${e}`);return}const s=I(t).size;this.activeEvent&&(this.activeEvent.jsonlBaseOffset=s);let n=null;try{this.activeEventPostReplyWatcher=Et(t,()=>{n&&clearTimeout(n),n=setTimeout(()=>this.finalizeIfEndTurn(e),300)}),this.activeEventPostReplyPoll=setInterval(()=>this.finalizeIfEndTurn(e),ni),o.info("claude-adapter",`JSONL watcher started for ${e} at ${t} (baseOffset=${s})`)}catch(r){o.warn("claude-adapter",`JSONL watcher start failed for ${e}: ${r}`)}}finalizeIfEndTurn(e){if(this.stopped||this.activeEvent?.eventId!==e||this.isChannelEventPrePush(this.activeEvent)||!this.claudeCliSessionId||!this.claudeSessionCwd)return;const t=W(this.claudeCliSessionId,this.claudeSessionCwd,this.activeEvent.jsonlBaseOffset);if(t?.apiError){this.activeEvent.apiFormatError=!0,t.apiErrorText&&(this.activeEvent.apiErrorText=t.apiErrorText);return}if(t&&t.stopReason==="end_turn"){o.info("claude-adapter",`JSONL watcher: end_turn detected for ${e}, finalizing as responded`);const i=this.activeEvent.sessionId;this.flushForwardedTail(e),this.bridgeCallbacks.sendEventResult(e,"responded",void 0,void 0),this.clearActiveEvent(),this.armStopHookBarrier(i);return}if(this.activeEvent?.replied&&this.activeEvent.jsonlBaseOffset!==void 0&&$e(this.claudeCliSessionId,this.claudeSessionCwd,this.activeEvent.jsonlBaseOffset,!0)){if(this.lastPtyOutputAt>0&&Date.now()-this.lastPtyOutputAt<Ke)return;o.info("claude-adapter",`JSONL poll: terminal tool_result detected for ${e} (file quiet, no end_turn), finalizing as responded`),this.flushForwardedTail(e),this.bridgeCallbacks.sendEventResult(e,"responded",void 0,void 0),this.clearActiveEvent()}}startPostReplyDeadline(e){this.clearActiveEventPostReplyTimer(),this.clearActiveEventHardTimer(),this.activeEventPostReplyTimer=setTimeout(()=>{if(this.stopped||this.activeEvent?.eventId!==e||!this.activeEvent?.replied&&!this.activeEvent?.textForwarded)return;if(this.shouldExtendByLiveness(e)){this.startPostReplyDeadline(e);return}const t=this.activeEvent.repliedAt,i=Math.round((Date.now()-t)/1e3);o.info("claude-adapter",`Post-reply deadline reached for ${e} (${i}s since reply), completing as responded`),this.flushForwardedTail(e),this.bridgeCallbacks.sendEventResult(e,"responded",void 0,void 0),this.clearActiveEvent()},ei)}beginCompaction(){this.compacting||(this.compacting=!0,o.info("claude-adapter","Compaction started; gating input"),this.emit("pauseIntake","compaction"),this.resetCompactingActivityTimer())}resetCompactingActivityTimer(){this.compactingTimer&&clearTimeout(this.compactingTimer),this.compactingTimer=setTimeout(()=>{o.warn("claude-adapter","Compaction stall: no PTY activity for 90s, emitting stuck"),this.emit("stuck")},9e4),this.compactingTimer.unref?.()}finishCompaction(e){if(!this.compacting)return;this.compacting=!1,this.compactingTimer&&(clearTimeout(this.compactingTimer),this.compactingTimer=null),o.info("claude-adapter",`Compaction finished (${e}); resuming intake`),this.emit("resumeIntake","compaction");const t=this.compactionDoneResolver;this.compactionDoneResolver=null,t?.(e)}armStopHookBarrier(e){this.stopHookBarrierTimer&&clearTimeout(this.stopHookBarrierTimer),this.stopHookBarrierSessionId=e,this.emit("pauseIntake","barrier"),this.stopHookBarrierTimer=setTimeout(()=>{this.stopHookBarrierSessionId===e&&(o.warn("claude-adapter",`Stop hook barrier timeout for session=${e}`),this.clearStopHookBarrier())},hi)}clearStopHookBarrier(){const e=this.stopHookBarrierSessionId!==null;this.stopHookBarrierSessionId=null,this.stopHookBarrierTimer&&(clearTimeout(this.stopHookBarrierTimer),this.stopHookBarrierTimer=null),e&&this.emit("resumeIntake","barrier")}clearPendingMcpFailureTimer(){this.pendingMcpFailureTimer&&(clearTimeout(this.pendingMcpFailureTimer),this.pendingMcpFailureTimer=null)}markMcpStartupFailure(){if(!this.mcpStartupFailureHandled&&(this.mcpStartupFailureHandled=!0,this.mcpChannelBroken=!0,o.error("claude-adapter","Claude reported blocking MCP server startup failure"),this.activeEvent)){const e=this.activeEvent.eventId,t=this.activeEvent.sessionId;this.bridgeCallbacks.sendStreamChunk(e,t,`
19
19
 
20
20
  Error: MCP server startup failed`,1,!1),this.bridgeCallbacks.sendEventResult(e,"failed","MCP server startup failed"),this.completedEventIds.set(e,Date.now()),this.clearActiveEvent()}}}class bi extends be{adapterSessionId;constructor(e){super(),this.adapterSessionId=e}emitDone(e){this.emit("done",e)}emitError(e){if(this.listenerCount("error")===0){o.warn("claude-adapter",`Prompt handle error (no listeners): ${e.message}`);return}this.emit("error",e)}async cancel(){}}function w(d){if(d==null)return 0;const e=Number(d);return Number.isFinite(e)?e:0}function it(d){if(typeof d!="string"||!d.trim())return;let e;try{e=JSON.parse(d)}catch{return}if(!Array.isArray(e))return;const t=e.filter(i=>{if(!i||typeof i!="object")return!0;const s=String(i.content??"");return!st(s)});return JSON.stringify(t)}const ki=600;function st(d){const e=d.trim();return e?e.includes("\u4ECE\u7EBF\u4E0A\u8BB0\u5F55\u6062\u590D")||e.includes("\u672C\u4F1A\u8BDD\u7684\u672C\u5730\u5386\u53F2\u8BB0\u5F55\u5DF2\u4E22\u5931")||e.includes("\u672C\u5730\u5386\u53F2\u8BB0\u5F55\u5DF2\u4E22\u5931")||e.includes("\u65E0\u6CD5\u7EED\u63A5\u4E4B\u524D\u7684\u4E0A\u4E0B\u6587")?!0:e.length>ki?!1:/历史[^\n]{0,40}丢失/.test(e)&&/新建话题|恢复|怎么处理|如何继续|选哪个/.test(e):!1}function Ri(d){const e=d.type;if(e!=="user"&&e!=="assistant")return[];const t=d.message,i=t&&typeof t=="object"?t.content:d.content;if(typeof i=="string")return[i];if(!Array.isArray(i))return[];const s=[];for(const n of i){if(!n||typeof n!="object")continue;const r=n;r.type==="text"&&typeof r.text=="string"&&s.push(r.text)}return s}function Di(d){for(const e of d.split(`
21
21
  `)){const t=e.trim();if(!t)continue;let i;try{i=JSON.parse(t)}catch{continue}if(!(!i||typeof i!="object")){for(const s of Ri(i))if(st(s))return!0}}return!1}function Z(d){return String(d).replace(/\\/g,"\\\\").replace(/\{/g,"\\{").replace(/\}/g,"\\}")}const ee={noFirst:"(?i)No,.{0,48}exit.*Yes,.{0,48}I.{0,48}trust(?:.*confirm)?",yesFirst:"(?i)Yes,.{0,48}(proceed|I.{0,48}trust).*No,.{0,48}exit(?:.*confirm)?"};function nt(d){return new RegExp(d.replace(/^\(\?i\)/,""),"is")}const $i=nt(ee.noFirst),Oi=nt(ee.yesFirst);async function xi(d,e,t){const i=f(d,"claude.expect"),s=f(d,"claude.pid"),n=["log_user 1","set timeout -1","set startup_prompt_armed 1",`set cmd_fifo {${Z(f(d,"cmd.fifo"))}}`,"file delete -force $cmd_fifo","exec mkfifo $cmd_fifo","set cmd_fd [open $cmd_fifo r+]","fconfigure $cmd_fd -blocking 0 -buffering line",`set claude_command [list {${Z(e)}}${t.map(r=>` {${Z(r)}}`).join("")}]`,"spawn -noecho {*}$claude_command",`set pid_file [open {${Z(s)}} w]`,"puts $pid_file [exp_pid -i $spawn_id]","close $pid_file","after 500",'send -- "\\r"',"expect {",` -re {${ee.noFirst}} {`,' if {$startup_prompt_armed} { after 1000; send -- "\\033\\[B"; after 400; send -- "\\r"; after 300 }; exp_continue'," }",` -re {${ee.yesFirst}} {`,' if {$startup_prompt_armed} { after 1000; send -- "\\r"; after 300 }; exp_continue'," }"," -re {(?i)(I am using this for local development|Please use --channels|dangerously-load-development-channels)} {",' if {$startup_prompt_armed} { after 1000; send -- "\\r"; after 600; send -- "\\r"; after 300 }; exp_continue'," }"," -re {(?i)(Enter.*confirm|Press.*Enter|Hit.*Enter|Continue.*Enter)} {",' if {$startup_prompt_armed} { send -- "\\r"; after 300 }; exp_continue'," }"," -re {(?i)Listening.*channel messages.*server:grix} {"," set startup_prompt_armed 0"," }"," eof {}","}","proc handle_cmd {} {"," upvar cmd_fd cmd_fd cmd_fifo cmd_fifo"," if {[catch {gets $cmd_fd} __line]} { return }"," if {[eof $cmd_fd]} {"," catch {close $cmd_fd}"," if {[catch {set cmd_fd [open $cmd_fifo r+]} err]} { return }"," fconfigure $cmd_fd -blocking 0 -buffering line"," fileevent $cmd_fd readable handle_cmd",` } elseif {$__line eq "${fe}enter"} {`,' send -- "\\r"',` } elseif {$__line eq "${fe}esc"} {`,' send -- "\\033"',' } elseif {$__line ne ""} {',' send -- "$__line\\r"'," }","}","fileevent $cmd_fd readable handle_cmd","expect_background {"," -re .+ { }"," eof { set ::__done 1 }","}","set ::__done 0","vwait ::__done",""];return await D(s,"","utf8"),await D(i,n.join(`
@@ -1,15 +1,15 @@
1
- import{execFileSync as Z}from"node:child_process";import{createInterface as q}from"node:readline";import{EventEmitter as E}from"node:events";import{randomUUID as ee}from"node:crypto";import{mkdirSync as L,readFileSync as P,writeFileSync as D,readdirSync as te,statSync as N}from"node:fs";import{join as A,resolve as se}from"node:path";import{fileURLToPath as ne}from"node:url";import{homedir as F}from"node:os";import{GRIX_PATHS as ie,log as h}from"../../core/log/index.js";import{IdentityInjector as re}from"../shared/identity-injector.js";import{buildSimpleProbeReport as oe}from"../shared/probe-util.js";import{InternalApiServer as ae}from"../../core/mcp/internal-api-server.js";import{resolveMcpServerName as de,pruneLegacyMcpServerEntries as le}from"../../core/mcp/server-name.js";import{InboundMessageIdInjector as ce}from"../shared/inbound-message-id-injector.js";import{insertBeforeTrailingMessageReferences as R}from"../../core/protocol/message-reference.js";import{syncDefaultSkillsToDir as ue}from"../../default-skills/index.js";import{spawnCommand as M,killProcessGroup as I}from"../../core/runtime/spawn.js";import{SessionBindingStore as he}from"../../core/persistence/session-binding-store.js";import{fetchCursorParameterizedModels as me}from"./fetch-models.js";import{resolveCursorSpawn as B}from"./resolve-command.js";import{fetchCursorAccountUsage as fe}from"./fetch-usage.js";import{buildCursorContextWindow as pe,buildCursorContextWindowFromUsage as ge,resolveCursorContextWindowSize as Se}from"./context-window.js";const ye=12e4,x=300*1e3,j=90*1e3,Q=1800*1e3,ve=8192,O="Summarize this conversation for handoff to a fresh chat. Include: goals, key decisions, important file paths, current status, and next steps. Be concise but complete. Output ONLY the summary text.",W=12e4,U=new Set(["completed","failed","error","errored","canceled","cancelled","rejected"]);function _e(d){const e=d.replace(/ToolCall$/i,"").trim();return e?e.charAt(0).toUpperCase()+e.slice(1):d||"tool"}function H(d){const e=String(d.call_id??d.callId??"").trim(),t=d.tool_call??d.toolCall;if(t&&typeof t=="object"&&!Array.isArray(t)){const n=Object.keys(t);if(n.length>0){const s=n[0],i=t[s],r=i&&typeof i=="object"&&!Array.isArray(i)?i:null;return{toolName:_e(s),toolInput:r?.args??r?.arguments??{},callId:e}}}return{toolName:String(d.name??d.tool_name??d.toolName??"tool").trim()||"tool",toolInput:d.args??d.tool_input??d.toolInput??{},callId:e}}function z(d){return String(d??"").trim().toLowerCase().replace(/[^a-z0-9]/g,"")}function J(d){const e=z(d);return e==="askquestion"||e==="askuserquestion"}function G(d){const e=z(d);return e==="createplan"||e==="exitplanmode"}function Ae(d){if(!Array.isArray(d))return;const e=d.map(t=>{if(typeof t=="string")return t.trim();if(!t||typeof t!="object")return"";const n=t,s=String(n.label??"").trim();if(s)return s;const i=String(n.value??"").trim();if(i)return i;const r=String(n.text??"").trim();return r||""}).filter(t=>t.length>0);return e.length>0?e:void 0}function be(d){if(!d||typeof d!="object"||Array.isArray(d))return null;const e=d,t=String(e.title??"").trim(),n=e.questions;if(!Array.isArray(n)||n.length===0)return null;const s=[];for(let i=0;i<n.length;i++){const r=n[i];if(!r||typeof r!="object")continue;const o=r,a=t||`Question ${i+1}`,l=String(o.header??o.id??o.question??a).trim()||a,c=String(o.prompt??o.question??"").trim();if(!c)continue;const u=Ae(o.options),p=o.multiSelect===!0||o.multi_select===!0;s.push({header:l,prompt:c,...u?{options:u}:{},...p?{multi_select:!0}:{}})}return s.length>0?s:null}function ke(d){if(!d||typeof d!="object"||Array.isArray(d))return null;const e=d,t=String(e.name??e.title??"").trim(),n=String(e.overview??"").trim(),s=String(e.plan??"").trim();if(!s&&!n&&!t)return null;const i=[];return t&&i.push(`# ${t}`),n&&i.push(n),s&&i.push(s),i.join(`
1
+ import{execFileSync as Z}from"node:child_process";import{createInterface as q}from"node:readline";import{EventEmitter as L}from"node:events";import{randomUUID as ee}from"node:crypto";import{mkdirSync as E,readFileSync as P,writeFileSync as D,readdirSync as te,statSync as N}from"node:fs";import{join as A,resolve as se}from"node:path";import{fileURLToPath as ne}from"node:url";import{homedir as F}from"node:os";import{GRIX_PATHS as ie,log as u}from"../../core/log/index.js";import{IdentityInjector as re}from"../shared/identity-injector.js";import{buildSimpleProbeReport as oe}from"../shared/probe-util.js";import{InternalApiServer as ae}from"../../core/mcp/internal-api-server.js";import{resolveMcpServerName as de,pruneLegacyMcpServerEntries as le}from"../../core/mcp/server-name.js";import{InboundMessageIdInjector as ce}from"../shared/inbound-message-id-injector.js";import{insertBeforeTrailingMessageReferences as R}from"../../core/protocol/message-reference.js";import{syncDefaultSkillsToDir as ue}from"../../default-skills/index.js";import{spawnCommand as B,killProcessGroup as I}from"../../core/runtime/spawn.js";import{countLivingDescendantsOutsideBaseline as he,listLivingDescendants as me}from"./living-descendants.js";import{SessionBindingStore as fe}from"../../core/persistence/session-binding-store.js";import{fetchCursorParameterizedModels as pe}from"./fetch-models.js";import{resolveCursorSpawn as M}from"./resolve-command.js";import{fetchCursorAccountUsage as ge}from"./fetch-usage.js";import{buildCursorContextWindow as Se,buildCursorContextWindowFromUsage as ye,resolveCursorContextWindowSize as ve}from"./context-window.js";const _e=12e4,x=300*1e3,j=90*1e3,O=1800*1e3,Ae=8192,Q="Summarize this conversation for handoff to a fresh chat. Include: goals, key decisions, important file paths, current status, and next steps. Be concise but complete. Output ONLY the summary text.",W=12e4,U=new Set(["completed","failed","error","errored","canceled","cancelled","rejected"]);function be(d){const e=d.replace(/ToolCall$/i,"").trim();return e?e.charAt(0).toUpperCase()+e.slice(1):d||"tool"}function z(d){const e=String(d.call_id??d.callId??"").trim(),t=d.tool_call??d.toolCall;if(t&&typeof t=="object"&&!Array.isArray(t)){const n=Object.keys(t);if(n.length>0){const s=n[0],i=t[s],r=i&&typeof i=="object"&&!Array.isArray(i)?i:null;return{toolName:be(s),toolInput:r?.args??r?.arguments??{},callId:e}}}return{toolName:String(d.name??d.tool_name??d.toolName??"tool").trim()||"tool",toolInput:d.args??d.tool_input??d.toolInput??{},callId:e}}function H(d){return String(d??"").trim().toLowerCase().replace(/[^a-z0-9]/g,"")}function J(d){const e=H(d);return e==="askquestion"||e==="askuserquestion"}function G(d){const e=H(d);return e==="createplan"||e==="exitplanmode"}function we(d){if(!Array.isArray(d))return;const e=d.map(t=>{if(typeof t=="string")return t.trim();if(!t||typeof t!="object")return"";const n=t,s=String(n.label??"").trim();if(s)return s;const i=String(n.value??"").trim();if(i)return i;const r=String(n.text??"").trim();return r||""}).filter(t=>t.length>0);return e.length>0?e:void 0}function ke(d){if(!d||typeof d!="object"||Array.isArray(d))return null;const e=d,t=String(e.title??"").trim(),n=e.questions;if(!Array.isArray(n)||n.length===0)return null;const s=[];for(let i=0;i<n.length;i++){const r=n[i];if(!r||typeof r!="object")continue;const o=r,a=t||`Question ${i+1}`,l=String(o.header??o.id??o.question??a).trim()||a,c=String(o.prompt??o.question??"").trim();if(!c)continue;const h=we(o.options),p=o.multiSelect===!0||o.multi_select===!0;s.push({header:l,prompt:c,...h?{options:h}:{},...p?{multi_select:!0}:{}})}return s.length>0?s:null}function Ie(d){if(!d||typeof d!="object"||Array.isArray(d))return null;const e=d,t=String(e.name??e.title??"").trim(),n=String(e.overview??"").trim(),s=String(e.plan??"").trim();if(!s&&!n&&!t)return null;const i=[];return t&&i.push(`# ${t}`),n&&i.push(n),s&&i.push(s),i.join(`
2
2
 
3
- `)}function we(d){if(!d||typeof d!="object"||Array.isArray(d))return null;const e=d,t=String(e.type??"").trim().toLowerCase();if(!t)return null;switch(t){case"tool_call":{const n=String(e.subtype??"").trim().toLowerCase(),s=H(e);return J(s.toolName)||G(s.toolName)?null:n==="started"||n==="start"?{type:"tool_call",payload:{tool_name:s.toolName,...s.callId?{tool_call_id:s.callId}:{}}}:(U.has(n),null)}case"error":return{type:"error",payload:{message:String(e.message??e.error??e.result??"cursor error").trim()||"cursor error"}};case"result":{const n=String(e.subtype??"").trim().toLowerCase();return e.is_error===!0||n==="error"?{type:"error",payload:{message:String(e.result??e.message??"cursor run failed").trim()||"cursor run failed"}}:null}default:return null}}function Ie(d,e){if(!d||typeof d!="object"||Array.isArray(d))return"";const t=d;if(String(t.type??"")!=="assistant")return"";const n=t.model_call_id??t.modelCallId;if(n!=null&&String(n).trim()!=="")return"";const s=t.message,i=s&&typeof s=="object"&&!Array.isArray(s)?s.content:void 0;if(!Array.isArray(i))return"";const r=i.map(o=>{if(!o||typeof o!="object"||Array.isArray(o))return"";const a=o;return a.type==="text"?String(a.text??""):""}).filter(Boolean).join("");if(!r)return"";if(e){if(r===e||e.startsWith(r)&&r.length<e.length)return"";if(r.startsWith(e))return r.slice(e.length)}return r}const X=[{id:"approval",displayName:"\u4EBA\u5DE5\u786E\u8BA4"},{id:"full_auto",displayName:"\u81EA\u7531\u6A21\u5F0F"},{id:"plan",displayName:"\u8BA1\u5212\u6A21\u5F0F"}];function C(d){const e=String(d??"").trim().toLowerCase();return X.some(t=>t.id===e)?e:null}function T(d){try{return process.kill(d,0),!0}catch(e){return e.code==="EPERM"}}function K(d,e,t){if(t<0)return null;let n;try{n=te(d,{withFileTypes:!0})}catch{return null}for(const s of n){const i=A(d,s.name);if(s.isDirectory()){const r=K(i,e,t-1);if(r)return r}else if(s.isFile()&&s.name.endsWith(e))return i}return null}function V(d,e){if(!d)return null;const t=e||A(F(),".cursor","projects");return K(t,`${d}.jsonl`,6)}function Ce(d){const e=d.context_messages_json;if(e)try{const t=JSON.parse(e);return!Array.isArray(t)||t.length===0?void 0:t.map(n=>({senderId:String(n.sender_id??"unknown"),content:String(n.content??"")}))}catch{return}}function Re(d,e){const t=[String(d??"")],n=String(e.quoted_message_id??"").trim();n&&t.push(`[quoted_message_id] ${n}`);const s=String(e.attachments_json??"").trim();return s&&t.push(`[attachments_json]
3
+ `)}function Ce(d){if(!d||typeof d!="object"||Array.isArray(d))return null;const e=d,t=String(e.type??"").trim().toLowerCase();if(!t)return null;switch(t){case"tool_call":{const n=String(e.subtype??"").trim().toLowerCase(),s=z(e);return J(s.toolName)||G(s.toolName)?null:n==="started"||n==="start"?{type:"tool_call",payload:{tool_name:s.toolName,...s.callId?{tool_call_id:s.callId}:{}}}:(U.has(n),null)}case"error":return{type:"error",payload:{message:String(e.message??e.error??e.result??"cursor error").trim()||"cursor error"}};case"result":{const n=String(e.subtype??"").trim().toLowerCase();return e.is_error===!0||n==="error"?{type:"error",payload:{message:String(e.result??e.message??"cursor run failed").trim()||"cursor run failed"}}:null}default:return null}}function Re(d,e){if(!d||typeof d!="object"||Array.isArray(d))return"";const t=d;if(String(t.type??"")!=="assistant")return"";const n=t.model_call_id??t.modelCallId;if(n!=null&&String(n).trim()!=="")return"";const s=t.message,i=s&&typeof s=="object"&&!Array.isArray(s)?s.content:void 0;if(!Array.isArray(i))return"";const r=i.map(o=>{if(!o||typeof o!="object"||Array.isArray(o))return"";const a=o;return a.type==="text"?String(a.text??""):""}).filter(Boolean).join("");if(!r)return"";if(e){if(r===e||e.startsWith(r)&&r.length<e.length)return"";if(r.startsWith(e))return r.slice(e.length)}return r}const X=[{id:"approval",displayName:"\u4EBA\u5DE5\u786E\u8BA4"},{id:"full_auto",displayName:"\u81EA\u7531\u6A21\u5F0F"},{id:"plan",displayName:"\u8BA1\u5212\u6A21\u5F0F"}];function C(d){const e=String(d??"").trim().toLowerCase();return X.some(t=>t.id===e)?e:null}function T(d){try{return process.kill(d,0),!0}catch(e){return e.code==="EPERM"}}function K(d,e,t){if(t<0)return null;let n;try{n=te(d,{withFileTypes:!0})}catch{return null}for(const s of n){const i=A(d,s.name);if(s.isDirectory()){const r=K(i,e,t-1);if(r)return r}else if(s.isFile()&&s.name.endsWith(e))return i}return null}function V(d,e){if(!d)return null;const t=e||A(F(),".cursor","projects");return K(t,`${d}.jsonl`,6)}function Be(d){const e=d.context_messages_json;if(e)try{const t=JSON.parse(e);return!Array.isArray(t)||t.length===0?void 0:t.map(n=>({senderId:String(n.sender_id??"unknown"),content:String(n.content??"")}))}catch{return}}function Me(d,e){const t=[String(d??"")],n=String(e.quoted_message_id??"").trim();n&&t.push(`[quoted_message_id] ${n}`);const s=String(e.attachments_json??"").trim();return s&&t.push(`[attachments_json]
4
4
  ${s}`),t.filter(i=>i.length>0).join(`
5
5
 
6
- `)}class Y extends E{adapterSessionId;constructor(e){super(),this.adapterSessionId=e}emitDone(e){this.emit("done",e)}emitError(e){if(this.listenerCount("error")===0){h.warn("cursor-adapter",`Prompt handle error (no listeners): ${e.message}`);return}this.emit("error",e)}async cancel(){}}class $ extends E{type="cursor";static STDERR_MAX_CHARS=8192;config;callbacks;alive=!1;stopped=!1;permissionHandler=null;internalApi=null;bindingStore=null;identity;inboundMessageIds=new ce;inboundQueue=[];pendingBySession=new Map;sessions=new Set;sessionRuntime=new Map;sessionSeq=0;lastUsageBySession=new Map;auditCaptures=new Map;completedAuditBoundaries=new Map;lastContextBySession=new Map;pendingHandoffBySession=new Map;pendingQuestionByRequestId=new Map;pendingQuestionAnswerBySession=new Map;cachedAccountRateLimits=null;rateLimitsInflight=null;_availableModels=[];transcriptPathBySession=new Map;activeBySession=new Map;constructor(e,t){super(),this.config=e,this.callbacks=t;const n=e.options??{};this.bindingStore=n.bindingStore instanceof he?n.bindingStore:null,this.identity=new re("cursor-adapter",t.getAgentProfile)}async start(){this.alive=!0,this.stopped=!1,(this.config.options??{}).mcp_tools!==!1&&(this.internalApi=new ae,this.internalApi.setInvokeHandler(async(t,n,s,i)=>this.callbacks.agentInvoke(t,n,i)),await this.internalApi.start()),this.notifyBindingReady(),this.refreshModels().then(()=>this.notifyBindingReady()).catch(()=>{}),this.refreshAccountRateLimits().then(()=>this.notifyBindingReady()).catch(()=>{})}get availableModels(){return this._availableModels}get availableModes(){return X.map(e=>({...e}))}async refreshModels(){try{const e={...process.env,...this.config.env??{}},t=await me({command:this.config.command,env:e,cwd:process.cwd()});t.length>0&&(this._availableModels=t,h.info("cursor-adapter",`Loaded ${t.length} available models`))}catch(e){h.warn("cursor-adapter",`Failed to refresh models: ${e instanceof Error?e.message:String(e)}`)}}async stop(){this.stopped=!0,this.alive=!1;for(const e of this.activeBySession.values())I(e.child,"SIGTERM");this.activeBySession.clear(),this.transcriptPathBySession.clear(),this.internalApi&&(await this.internalApi.stop(),this.internalApi=null)}isAlive(){return this.alive}async createSession(e){const t=`cursor-${Date.now()}-${++this.sessionSeq}`;return this.sessions.add(t),this.sessionRuntime.set(t,{cwd:typeof e.cwd=="string"?e.cwd:void 0,modelId:typeof e.modelId=="string"?e.modelId:void 0,modeId:typeof e.modeId=="string"?e.modeId:void 0}),t}async resumeSession(e,t){this.sessions.add(e);const n=this.sessionRuntime.get(e)??{};this.sessionRuntime.set(e,{...n,cwd:typeof t?.cwd=="string"?t.cwd:n.cwd,modelId:typeof t?.modelId=="string"?t.modelId:n.modelId,modeId:typeof t?.modeId=="string"?t.modeId:n.modeId})}onAgentProfileChanged(){this.identity.onProfileChanged()}async destroySession(e){this.sessions.delete(e),this.sessionRuntime.delete(e),this.transcriptPathBySession.delete(e),this.identity.forgetSession(e),this.inboundMessageIds.forgetSession(e),this.pendingQuestionAnswerBySession.delete(e),this.pendingHandoffBySession.delete(e);for(const[t,n]of this.pendingQuestionByRequestId)n.sessionId===e&&this.pendingQuestionByRequestId.delete(t)}sendPrompt(e){const t=new Y(e.adapterSessionId);if(!this.alive||this.stopped)return queueMicrotask(()=>t.emitError(new Error("adapter not running"))),t;const n=this.inboundQueue.shift()??{event_id:`cursor-evt-${Date.now()}`,session_id:e.adapterSessionId,content:e.text},s=e.adapterSessionId,i=this.pendingBySession.get(s)??[];return i.push({event:n,request:e,handle:t}),this.pendingBySession.set(s,i),this.tryStartNext(s),t}tryStartNext(e){if(this.activeBySession.has(e)||!this.alive||this.stopped)return;const t=this.pendingBySession.get(e);if(!t||t.length===0)return;const n=t.shift();t.length===0?this.pendingBySession.delete(e):this.pendingBySession.set(e,t),n&&this.startJob(n,!1,0)}startJob(e,t,n){const{event:s,request:i,handle:r}=e;s.audit?.enabled&&!this.auditCaptures.has(s.event_id)&&this.auditCaptures.set(s.event_id,[]);const o=i.adapterSessionId,a=this.sessionRuntime.get(i.adapterSessionId)??{},l=this.config.options??{},c=[...this.config.args??[]];c.push("-p","--output-format","stream-json"),l.trust!==!1&&c.push("--trust");const u=a.cwd||l.workspace;this.internalApi&&u&&(this.ensureWorkspaceMcpAndSkills(u),c.push("--approve-mcps")),u&&c.push("--workspace",u);const p=a.modelId||l.model;p&&c.push("--model",p);const _=C(a.modeId||l.mode)??"full_auto";_==="full_auto"?c.push("--yolo"):_==="plan"&&c.push("--plan");const v=a.cursorSessionId??this.bindingStore?.getAcpSessionId(i.adapterSessionId),b=!!(v&&l.use_continue!==!1&&!t);b&&c.push("--resume",v),c.push(this.buildPromptText(i));const f={...process.env,...this.config.env??{}},y=B(this.config.command,f);h.info("cursor-adapter",`job start: event=${s.event_id} session=${s.session_id} retry=${n}`);let g;try{g=M(y.command,[...y.prefixArgs,...c],{cwd:u||process.cwd(),env:y.env,stdio:["ignore","pipe","pipe"]}).process}catch(S){this.finishActive(o,"failed",`spawn failed: ${S instanceof Error?S.message:String(S)}`);return}this.callbacks.sendEventAck(s.event_id,s.session_id),this.emit("eventStarted",s.event_id,s.session_id);const m={event:s,request:i,handle:r,child:g,seq:0,done:!1,stderr:"",timer:null,idleTimer:null,toolCallsInFlight:new Set,toolCallsStarted:0,assistantTextEmitted:"",streamBuffer:"",lastIoAt:Date.now(),sawResult:!1,retryCount:n,usedContinue:b,workspace:u||process.cwd(),args:c};this.activeBySession.set(o,m),this.notifyBindingReady(o),this.armActiveIdleTimer(o);const k=i.timeoutMs&&i.timeoutMs>0?i.timeoutMs:0;k>0&&(m.timer=setTimeout(()=>{m.done||(I(g,"SIGTERM"),this.finishActive(o,"failed",`cursor agent timeout after ${k}ms`))},k)),q({input:g.stdout}).on("line",S=>this.handleStdoutLineForActive(m,S)),g.stderr?.on("data",S=>{if(m.done)return;const w=m.stderr+String(S??"");m.stderr=w.length>$.STDERR_MAX_CHARS?w.slice(-$.STDERR_MAX_CHARS):w,m.lastIoAt=Date.now(),this.armActiveIdleTimer(o)}),g.once("error",S=>{this.finishActive(o,"failed",`spawn failed: ${String(S?.message??S)}`)}),g.once("close",S=>{if(!m.done)if((S??0)===0)this.finishActive(o,"responded");else{if(this.shouldRetryWithoutContinue(m)){m.timer&&clearTimeout(m.timer),m.idleTimer&&clearTimeout(m.idleTimer),this.activeBySession.delete(o),this.startJob(e,!0,m.retryCount+1);return}const w=m.stderr.trim()||`cursor agent exited with code ${S??-1}`;this.finishActive(o,"failed",w)}})}async cancel(e){const t=this.activeBySession.get(e);t&&(I(t.child,"SIGTERM"),this.finishActive(e,"canceled","canceled"))}deliverInboundEvent(e){const t=String(e.session_id??"").trim();if(!t){h.warn("cursor-adapter",`Dropping event ${e.event_id}: missing session_id`),this.callbacks.sendEventResult(e.event_id,"failed","missing session_id");return}if(!this.alive||this.stopped){h.warn("cursor-adapter",`Dropping event ${e.event_id}: adapter not running`),this.callbacks.sendEventAck(e.event_id,t),this.callbacks.sendEventResult(e.event_id,"failed","adapter not running");return}const n={adapterSessionId:t,text:this.inboundMessageIds.inject(t,Re(String(e.content??""),e),e.msg_id),contextMessages:Ce(e)},s=new Y(t),i=this.pendingBySession.get(t)??[];i.push({event:e,request:n,handle:s}),this.pendingBySession.set(t,i),h.info("cursor-adapter",`inbound queued: event=${e.event_id} session=${t} depth=${i.length}`),this.tryStartNext(t)}cancelSessionWork(e,t){const n=this.pendingBySession.get(e)??[];this.pendingBySession.delete(e);const s=this.activeBySession.get(e);s&&(this.killChildWithFallback(s.child),this.finishActive(e,"canceled",t));for(const i of n)this.callbacks.sendEventAck(i.event.event_id,i.event.session_id),this.callbacks.sendEventResult(i.event.event_id,"canceled",t),i.handle.emitDone({status:"canceled",error:t})}killChildWithFallback(e){I(e,"SIGTERM"),setTimeout(()=>{if(e.exitCode===null&&e.signalCode===null)try{I(e,"SIGKILL")}catch{}},5e3).unref()}deliverStopEvent(e,t){if(t){const n=this.activeBySession.get(t);if(n?.event.event_id===e){this.killChildWithFallback(n.child),this.finishActive(t,"canceled","stopped");return}const s=this.pendingBySession.get(t)??[],i=s.findIndex(r=>r.event.event_id===e);if(i>=0){const[r]=s.splice(i,1);s.length===0?this.pendingBySession.delete(t):this.pendingBySession.set(t,s),this.callbacks.sendEventAck(r.event.event_id,r.event.session_id),this.callbacks.sendEventResult(r.event.event_id,"canceled","stopped"),r.handle.emitDone({status:"canceled",error:"stopped"})}return}for(const[n,s]of this.activeBySession.entries())if(s.event.event_id===e){this.killChildWithFallback(s.child),this.finishActive(n,"canceled","stopped");return}for(const[n,s]of this.pendingBySession.entries()){const i=s.findIndex(o=>o.event.event_id===e);if(i<0)continue;const[r]=s.splice(i,1);s.length===0?this.pendingBySession.delete(n):this.pendingBySession.set(n,s),this.callbacks.sendEventAck(r.event.event_id,r.event.session_id),this.callbacks.sendEventResult(r.event.event_id,"canceled","stopped"),r.handle.emitDone({status:"canceled",error:"stopped"});return}}async handleLocalAction(e){const t=String(e.action_type??"").trim().toLowerCase(),n=e.params??{},s=String(n.session_id??"").trim(),i=e.action_id;if(t==="claude_interaction_reply")return String(n.kind??"").trim()==="permission"?(this.callbacks.sendLocalActionResult(i,"failed",void 0,"unsupported","Cursor adapter does not support interactive permission approvals"),{handled:!0,kind:"permission_unsupported"}):this.handleQuestionReply(e);switch(t){case"set_model":{const r=String(n.model_id??"").trim();if(!r||!s)return this.callbacks.sendLocalActionResult(i,"failed",void 0,"invalid_params","model_id and session_id are required"),{handled:!0,kind:"set_model"};const o=this.sessionRuntime.get(s)??{};this.sessionRuntime.set(s,{...o,modelId:r}),this.notifyBindingReady(s);const a=this.getToolbarMeta(s);return this.callbacks.sendLocalActionResult(i,"ok",{outcome:"model_set",session_context:{model_id:r,mode_id:a.mode_id,modelId:r,modeId:a.mode_id},model_id:r,mode_id:a.mode_id,available_models:this.availableModels,available_modes:this.availableModes}),{handled:!0,kind:"set_model"}}case"set_mode":{const r=C(n.mode_id);if(!r||!s)return this.callbacks.sendLocalActionResult(i,"failed",void 0,"invalid_params","mode_id must be approval, full_auto, or plan and session_id is required"),{handled:!0,kind:"set_mode"};const o=this.sessionRuntime.get(s)??{};this.sessionRuntime.set(s,{...o,modeId:r}),this.notifyBindingReady(s);const a=this.getToolbarMeta(s);return this.callbacks.sendLocalActionResult(i,"ok",{outcome:"mode_set",session_context:{model_id:a.model_id,mode_id:r,modelId:a.model_id,modeId:r},model_id:a.model_id,mode_id:r,available_models:this.availableModels,available_modes:this.availableModes}),{handled:!0,kind:"set_mode"}}case"get_context":{const r=s?this.sessionRuntime.get(s):void 0,o=this.config.options??{},a=this.getToolbarMeta(s);return this.callbacks.sendLocalActionResult(i,"ok",{session_context:{model_id:a.model_id,mode_id:a.mode_id,cwd:r?.cwd??o.workspace??null},model_id:a.model_id,mode_id:a.mode_id,modelId:a.model_id,modeId:a.mode_id,cwd:r?.cwd??o.workspace??null,available_models:this.availableModels,available_modes:this.availableModes}),{handled:!0,kind:"get_context"}}case"get_rate_limits":{await this.refreshAccountRateLimits({force:!0});const r=this.getRateLimitsSnapshot(s||void 0);return this.callbacks.sendLocalActionResult(i,"ok",r),s&&this.notifyBindingReady(s),{handled:!0,kind:"get_rate_limits"}}case"get_session_usage":{const r=s?this.getUsageSnapshot(s):null;if(r){const o={inputTokens:r.total.input,outputTokens:r.total.output,cacheReadInputTokens:r.total.cacheRead,cacheCreationInputTokens:r.total.cacheWrite};this.callbacks.sendLocalActionResult(i,"ok",{sessionId:s,adapterType:"cursor",sampledAt:r.sampledAt,turns:r.turns,total:o,models:[{model:this.getToolbarMeta(s).model_id??"auto",turns:r.turns,total:o}]})}else this.callbacks.sendLocalActionResult(i,"failed",void 0,"usage_not_found","No usage data found for this session");return{handled:!0,kind:"get_session_usage"}}case"session_control":{const r=String(n.verb??"").trim().toLowerCase();if(r==="restart"&&s)this.cancelSessionWork(s,"restarted"),this.callbacks.sendLocalActionResult(i,"ok",{outcome:"restarted"});else if(r==="status"){const o=this.sessionRuntime.get(s),a=this.config.options??{},l=this.getToolbarMeta(s),c=this.activeBySession.has(s);this.callbacks.sendLocalActionResult(i,"ok",{verb:"status",status:c?"running":"idle",session_context:{model_id:l.model_id,mode_id:l.mode_id,cwd:o?.cwd??a.workspace??null},model_id:l.model_id,mode_id:l.mode_id,modelId:l.model_id,modeId:l.mode_id,cwd:o?.cwd??a.workspace??null,available_models:this.availableModels,available_modes:this.availableModes})}else this.callbacks.sendLocalActionResult(i,"failed",void 0,"invalid_verb",`Unsupported verb: ${r}`);return{handled:!0,kind:"session_control"}}default:return{handled:!1,kind:"unsupported"}}}getSupportedCommands(){return[{name:"compact",description:"\u538B\u7F29\u4E0A\u4E0B\u6587",args:"[instructions]"}]}async execCommand(e,t,n){return e!=="compact"?{status:"unsupported",message:`Unsupported command: ${e}`}:this.runCompactHandoff(n,t)}async runCompactHandoff(e,t=""){if(!e)return{status:"failed",message:"session_id is required"};if(this.activeBySession.has(e))return{status:"failed",message:"Cannot compact while a Cursor turn is active"};const n=this.sessionRuntime.get(e)??{},s=this.config.options??{},i=n.cursorSessionId??this.bindingStore?.getAcpSessionId(e)??null;if(!i)return{status:"failed",message:"No Cursor chat to compact (send a message first)"};const r=String(n.cwd||s.workspace||process.cwd()),o=String(n.modelId||s.model||""),a=C(n.modeId||s.mode)??"full_auto",l=t.trim(),c=l?`${O}
6
+ `)}class Y extends L{adapterSessionId;constructor(e){super(),this.adapterSessionId=e}emitDone(e){this.emit("done",e)}emitError(e){if(this.listenerCount("error")===0){u.warn("cursor-adapter",`Prompt handle error (no listeners): ${e.message}`);return}this.emit("error",e)}async cancel(){}}class $ extends L{type="cursor";static STDERR_MAX_CHARS=8192;config;callbacks;alive=!1;stopped=!1;permissionHandler=null;internalApi=null;bindingStore=null;identity;inboundMessageIds=new ce;inboundQueue=[];pendingBySession=new Map;sessions=new Set;sessionRuntime=new Map;sessionSeq=0;lastUsageBySession=new Map;auditCaptures=new Map;completedAuditBoundaries=new Map;lastContextBySession=new Map;pendingHandoffBySession=new Map;pendingQuestionByRequestId=new Map;pendingQuestionAnswerBySession=new Map;cachedAccountRateLimits=null;rateLimitsInflight=null;_availableModels=[];transcriptPathBySession=new Map;activeBySession=new Map;constructor(e,t){super(),this.config=e,this.callbacks=t;const n=e.options??{};this.bindingStore=n.bindingStore instanceof fe?n.bindingStore:null,this.identity=new re("cursor-adapter",t.getAgentProfile)}async start(){this.alive=!0,this.stopped=!1,(this.config.options??{}).mcp_tools!==!1&&(this.internalApi=new ae,this.internalApi.setInvokeHandler(async(t,n,s,i)=>this.callbacks.agentInvoke(t,n,i)),await this.internalApi.start()),this.notifyBindingReady(),this.refreshModels().then(()=>this.notifyBindingReady()).catch(()=>{}),this.refreshAccountRateLimits().then(()=>this.notifyBindingReady()).catch(()=>{})}get availableModels(){return this._availableModels}get availableModes(){return X.map(e=>({...e}))}async refreshModels(){try{const e={...process.env,...this.config.env??{}},t=await pe({command:this.config.command,env:e,cwd:process.cwd()});t.length>0&&(this._availableModels=t,u.info("cursor-adapter",`Loaded ${t.length} available models`))}catch(e){u.warn("cursor-adapter",`Failed to refresh models: ${e instanceof Error?e.message:String(e)}`)}}async stop(){this.stopped=!0,this.alive=!1;for(const e of this.activeBySession.values())I(e.child,"SIGTERM");this.activeBySession.clear(),this.transcriptPathBySession.clear(),this.internalApi&&(await this.internalApi.stop(),this.internalApi=null)}isAlive(){return this.alive}async createSession(e){const t=`cursor-${Date.now()}-${++this.sessionSeq}`;return this.sessions.add(t),this.sessionRuntime.set(t,{cwd:typeof e.cwd=="string"?e.cwd:void 0,modelId:typeof e.modelId=="string"?e.modelId:void 0,modeId:typeof e.modeId=="string"?e.modeId:void 0}),t}async resumeSession(e,t){this.sessions.add(e);const n=this.sessionRuntime.get(e)??{};this.sessionRuntime.set(e,{...n,cwd:typeof t?.cwd=="string"?t.cwd:n.cwd,modelId:typeof t?.modelId=="string"?t.modelId:n.modelId,modeId:typeof t?.modeId=="string"?t.modeId:n.modeId})}onAgentProfileChanged(){this.identity.onProfileChanged()}async destroySession(e){this.sessions.delete(e),this.sessionRuntime.delete(e),this.transcriptPathBySession.delete(e),this.identity.forgetSession(e),this.inboundMessageIds.forgetSession(e),this.pendingQuestionAnswerBySession.delete(e),this.pendingHandoffBySession.delete(e);for(const[t,n]of this.pendingQuestionByRequestId)n.sessionId===e&&this.pendingQuestionByRequestId.delete(t)}sendPrompt(e){const t=new Y(e.adapterSessionId);if(!this.alive||this.stopped)return queueMicrotask(()=>t.emitError(new Error("adapter not running"))),t;const n=this.inboundQueue.shift()??{event_id:`cursor-evt-${Date.now()}`,session_id:e.adapterSessionId,content:e.text},s=e.adapterSessionId,i=this.pendingBySession.get(s)??[];return i.push({event:n,request:e,handle:t}),this.pendingBySession.set(s,i),this.tryStartNext(s),t}tryStartNext(e){if(this.activeBySession.has(e)||!this.alive||this.stopped)return;const t=this.pendingBySession.get(e);if(!t||t.length===0)return;const n=t.shift();t.length===0?this.pendingBySession.delete(e):this.pendingBySession.set(e,t),n&&this.startJob(n,!1,0)}startJob(e,t,n){const{event:s,request:i,handle:r}=e;s.audit?.enabled&&!this.auditCaptures.has(s.event_id)&&this.auditCaptures.set(s.event_id,[]);const o=i.adapterSessionId,a=this.sessionRuntime.get(i.adapterSessionId)??{},l=this.config.options??{},c=[...this.config.args??[]];c.push("-p","--output-format","stream-json"),l.trust!==!1&&c.push("--trust");const h=a.cwd||l.workspace;this.internalApi&&h&&(this.ensureWorkspaceMcpAndSkills(h),c.push("--approve-mcps")),h&&c.push("--workspace",h);const p=a.modelId||l.model;p&&c.push("--model",p);const _=C(a.modeId||l.mode)??"full_auto";_==="full_auto"?c.push("--yolo"):_==="plan"&&c.push("--plan");const v=a.cursorSessionId??this.bindingStore?.getAcpSessionId(i.adapterSessionId),b=!!(v&&l.use_continue!==!1&&!t);b&&c.push("--resume",v),c.push(this.buildPromptText(i));const f={...process.env,...this.config.env??{}},y=M(this.config.command,f);u.info("cursor-adapter",`job start: event=${s.event_id} session=${s.session_id} retry=${n}`);let g;try{g=B(y.command,[...y.prefixArgs,...c],{cwd:h||process.cwd(),env:y.env,stdio:["ignore","pipe","pipe"]}).process}catch(S){this.finishActive(o,"failed",`spawn failed: ${S instanceof Error?S.message:String(S)}`);return}this.callbacks.sendEventAck(s.event_id,s.session_id),this.emit("eventStarted",s.event_id,s.session_id);const m={event:s,request:i,handle:r,child:g,seq:0,done:!1,stderr:"",timer:null,idleTimer:null,toolCallsInFlight:new Set,toolCallsStarted:0,assistantTextEmitted:"",streamBuffer:"",lastIoAt:Date.now(),sawResult:!1,descendantBaseline:void 0,retryCount:n,usedContinue:b,workspace:h||process.cwd(),args:c};this.activeBySession.set(o,m),this.notifyBindingReady(o),this.armActiveIdleTimer(o);const w=i.timeoutMs&&i.timeoutMs>0?i.timeoutMs:0;w>0&&(m.timer=setTimeout(()=>{m.done||(I(g,"SIGTERM"),this.finishActive(o,"failed",`cursor agent timeout after ${w}ms`))},w)),q({input:g.stdout}).on("line",S=>this.handleStdoutLineForActive(m,S)),g.stderr?.on("data",S=>{if(m.done)return;const k=m.stderr+String(S??"");m.stderr=k.length>$.STDERR_MAX_CHARS?k.slice(-$.STDERR_MAX_CHARS):k,m.lastIoAt=Date.now(),this.armActiveIdleTimer(o)}),g.once("error",S=>{this.finishActive(o,"failed",`spawn failed: ${String(S?.message??S)}`)}),g.once("close",S=>{if(!m.done)if((S??0)===0)this.finishActive(o,"responded");else{if(this.shouldRetryWithoutContinue(m)){m.timer&&clearTimeout(m.timer),m.idleTimer&&clearTimeout(m.idleTimer),this.activeBySession.delete(o),this.startJob(e,!0,m.retryCount+1);return}const k=m.stderr.trim()||`cursor agent exited with code ${S??-1}`;this.finishActive(o,"failed",k)}})}async cancel(e){const t=this.activeBySession.get(e);t&&(I(t.child,"SIGTERM"),this.finishActive(e,"canceled","canceled"))}deliverInboundEvent(e){const t=String(e.session_id??"").trim();if(!t){u.warn("cursor-adapter",`Dropping event ${e.event_id}: missing session_id`),this.callbacks.sendEventResult(e.event_id,"failed","missing session_id");return}if(!this.alive||this.stopped){u.warn("cursor-adapter",`Dropping event ${e.event_id}: adapter not running`),this.callbacks.sendEventAck(e.event_id,t),this.callbacks.sendEventResult(e.event_id,"failed","adapter not running");return}const n={adapterSessionId:t,text:this.inboundMessageIds.inject(t,Me(String(e.content??""),e),e.msg_id),contextMessages:Be(e)},s=new Y(t),i=this.pendingBySession.get(t)??[];i.push({event:e,request:n,handle:s}),this.pendingBySession.set(t,i),u.info("cursor-adapter",`inbound queued: event=${e.event_id} session=${t} depth=${i.length}`),this.tryStartNext(t)}cancelSessionWork(e,t){const n=this.pendingBySession.get(e)??[];this.pendingBySession.delete(e);const s=this.activeBySession.get(e);s&&(this.killChildWithFallback(s.child),this.finishActive(e,"canceled",t));for(const i of n)this.callbacks.sendEventAck(i.event.event_id,i.event.session_id),this.callbacks.sendEventResult(i.event.event_id,"canceled",t),i.handle.emitDone({status:"canceled",error:t})}killChildWithFallback(e){I(e,"SIGTERM"),setTimeout(()=>{if(e.exitCode===null&&e.signalCode===null)try{I(e,"SIGKILL")}catch{}},5e3).unref()}deliverStopEvent(e,t){if(t){const n=this.activeBySession.get(t);if(n?.event.event_id===e){this.killChildWithFallback(n.child),this.finishActive(t,"canceled","stopped");return}const s=this.pendingBySession.get(t)??[],i=s.findIndex(r=>r.event.event_id===e);if(i>=0){const[r]=s.splice(i,1);s.length===0?this.pendingBySession.delete(t):this.pendingBySession.set(t,s),this.callbacks.sendEventAck(r.event.event_id,r.event.session_id),this.callbacks.sendEventResult(r.event.event_id,"canceled","stopped"),r.handle.emitDone({status:"canceled",error:"stopped"})}return}for(const[n,s]of this.activeBySession.entries())if(s.event.event_id===e){this.killChildWithFallback(s.child),this.finishActive(n,"canceled","stopped");return}for(const[n,s]of this.pendingBySession.entries()){const i=s.findIndex(o=>o.event.event_id===e);if(i<0)continue;const[r]=s.splice(i,1);s.length===0?this.pendingBySession.delete(n):this.pendingBySession.set(n,s),this.callbacks.sendEventAck(r.event.event_id,r.event.session_id),this.callbacks.sendEventResult(r.event.event_id,"canceled","stopped"),r.handle.emitDone({status:"canceled",error:"stopped"});return}}async handleLocalAction(e){const t=String(e.action_type??"").trim().toLowerCase(),n=e.params??{},s=String(n.session_id??"").trim(),i=e.action_id;if(t==="claude_interaction_reply")return String(n.kind??"").trim()==="permission"?(this.callbacks.sendLocalActionResult(i,"failed",void 0,"unsupported","Cursor adapter does not support interactive permission approvals"),{handled:!0,kind:"permission_unsupported"}):this.handleQuestionReply(e);switch(t){case"set_model":{const r=String(n.model_id??"").trim();if(!r||!s)return this.callbacks.sendLocalActionResult(i,"failed",void 0,"invalid_params","model_id and session_id are required"),{handled:!0,kind:"set_model"};const o=this.sessionRuntime.get(s)??{};this.sessionRuntime.set(s,{...o,modelId:r}),this.notifyBindingReady(s);const a=this.getToolbarMeta(s);return this.callbacks.sendLocalActionResult(i,"ok",{outcome:"model_set",session_context:{model_id:r,mode_id:a.mode_id,modelId:r,modeId:a.mode_id},model_id:r,mode_id:a.mode_id,available_models:this.availableModels,available_modes:this.availableModes}),{handled:!0,kind:"set_model"}}case"set_mode":{const r=C(n.mode_id);if(!r||!s)return this.callbacks.sendLocalActionResult(i,"failed",void 0,"invalid_params","mode_id must be approval, full_auto, or plan and session_id is required"),{handled:!0,kind:"set_mode"};const o=this.sessionRuntime.get(s)??{};this.sessionRuntime.set(s,{...o,modeId:r}),this.notifyBindingReady(s);const a=this.getToolbarMeta(s);return this.callbacks.sendLocalActionResult(i,"ok",{outcome:"mode_set",session_context:{model_id:a.model_id,mode_id:r,modelId:a.model_id,modeId:r},model_id:a.model_id,mode_id:r,available_models:this.availableModels,available_modes:this.availableModes}),{handled:!0,kind:"set_mode"}}case"get_context":{const r=s?this.sessionRuntime.get(s):void 0,o=this.config.options??{},a=this.getToolbarMeta(s);return this.callbacks.sendLocalActionResult(i,"ok",{session_context:{model_id:a.model_id,mode_id:a.mode_id,cwd:r?.cwd??o.workspace??null},model_id:a.model_id,mode_id:a.mode_id,modelId:a.model_id,modeId:a.mode_id,cwd:r?.cwd??o.workspace??null,available_models:this.availableModels,available_modes:this.availableModes}),{handled:!0,kind:"get_context"}}case"get_rate_limits":{await this.refreshAccountRateLimits({force:!0});const r=this.getRateLimitsSnapshot(s||void 0);return this.callbacks.sendLocalActionResult(i,"ok",r),s&&this.notifyBindingReady(s),{handled:!0,kind:"get_rate_limits"}}case"get_session_usage":{const r=s?this.getUsageSnapshot(s):null;if(r){const o={inputTokens:r.total.input,outputTokens:r.total.output,cacheReadInputTokens:r.total.cacheRead,cacheCreationInputTokens:r.total.cacheWrite};this.callbacks.sendLocalActionResult(i,"ok",{sessionId:s,adapterType:"cursor",sampledAt:r.sampledAt,turns:r.turns,total:o,models:[{model:this.getToolbarMeta(s).model_id??"auto",turns:r.turns,total:o}]})}else this.callbacks.sendLocalActionResult(i,"failed",void 0,"usage_not_found","No usage data found for this session");return{handled:!0,kind:"get_session_usage"}}case"session_control":{const r=String(n.verb??"").trim().toLowerCase();if(r==="restart"&&s)this.cancelSessionWork(s,"restarted"),this.callbacks.sendLocalActionResult(i,"ok",{outcome:"restarted"});else if(r==="status"){const o=this.sessionRuntime.get(s),a=this.config.options??{},l=this.getToolbarMeta(s),c=this.activeBySession.has(s);this.callbacks.sendLocalActionResult(i,"ok",{verb:"status",status:c?"running":"idle",session_context:{model_id:l.model_id,mode_id:l.mode_id,cwd:o?.cwd??a.workspace??null},model_id:l.model_id,mode_id:l.mode_id,modelId:l.model_id,modeId:l.mode_id,cwd:o?.cwd??a.workspace??null,available_models:this.availableModels,available_modes:this.availableModes})}else this.callbacks.sendLocalActionResult(i,"failed",void 0,"invalid_verb",`Unsupported verb: ${r}`);return{handled:!0,kind:"session_control"}}default:return{handled:!1,kind:"unsupported"}}}getSupportedCommands(){return[{name:"compact",description:"\u538B\u7F29\u4E0A\u4E0B\u6587",args:"[instructions]"}]}async execCommand(e,t,n){return e!=="compact"?{status:"unsupported",message:`Unsupported command: ${e}`}:this.runCompactHandoff(n,t)}async runCompactHandoff(e,t=""){if(!e)return{status:"failed",message:"session_id is required"};if(this.activeBySession.has(e))return{status:"failed",message:"Cannot compact while a Cursor turn is active"};const n=this.sessionRuntime.get(e)??{},s=this.config.options??{},i=n.cursorSessionId??this.bindingStore?.getAcpSessionId(e)??null;if(!i)return{status:"failed",message:"No Cursor chat to compact (send a message first)"};const r=String(n.cwd||s.workspace||process.cwd()),o=String(n.modelId||s.model||""),a=C(n.modeId||s.mode)??"full_auto",l=t.trim(),c=l?`${Q}
7
7
 
8
8
  Additional focus from user:
9
- ${l}`:O;this.emit("pauseIntake","compaction"),h.info("cursor-adapter",`[compact] summarize resume=${i} session=${e}`);try{const u=await this.runPrintOnce({prompt:c,resumeChatId:i,cwd:r,modelId:o||void 0,modeId:a,timeoutMs:W});if(!u.ok||!u.text.trim())return{status:"failed",message:u.error??"Summarize failed (empty result)"};const p=await this.createEmptyChat();return p?(this.sessionRuntime.set(e,{...n,cwd:r,modelId:o||n.modelId,modeId:a,cursorSessionId:p}),this.transcriptPathBySession.delete(e),this.bindingStore?.setAcpSessionId(e,p),this.identity.forgetSession(e),this.inboundMessageIds.forgetSession(e),this.pendingHandoffBySession.set(e,u.text.trim()),this.lastContextBySession.set(e,pe(0,Se(o))),this.notifyBindingReady(e),h.info("cursor-adapter",`[compact] handoff ready: old=${i} new=${p} summaryChars=${u.text.trim().length}`),{status:"ok",message:"Context compacted via summarize + new chat",data:{previousChatId:i,chatId:p}}):{status:"failed",message:"agent create-chat failed"}}finally{this.emit("resumeIntake","compaction")}}runPrintOnce(e){const t={...process.env,...this.config.env??{}},n=B(this.config.command,t),s=[...n.prefixArgs,...this.config.args??[]];s.push("-p","--output-format","stream-json"),this.config.options?.trust!==!1&&s.push("--trust"),e.cwd&&s.push("--workspace",e.cwd),e.modelId&&s.push("--model",e.modelId);const i=C(e.modeId)??"full_auto";return i==="full_auto"?s.push("--yolo"):i==="plan"&&s.push("--plan"),e.resumeChatId&&s.push("--resume",e.resumeChatId),s.push(e.prompt),new Promise(r=>{let o;try{o=M(n.command,s,{cwd:e.cwd||process.cwd(),env:n.env,stdio:["ignore","pipe","pipe"]}).process}catch(f){r({ok:!1,text:"",error:`spawn failed: ${f instanceof Error?f.message:String(f)}`});return}let a="",l="",c="",u=!1;const p=e.timeoutMs&&e.timeoutMs>0?e.timeoutMs:W,_=setTimeout(()=>{u||(u=!0,this.killChildWithFallback(o),r({ok:!1,text:a,sessionId:l||void 0,error:`compact timeout after ${p}ms`}))},p);_.unref?.();const v=(f,y)=>{u||(u=!0,clearTimeout(_),r({ok:f,text:a,sessionId:l||void 0,error:y}))};q({input:o.stdout}).on("line",f=>{const y=f.trim();if(!y)return;let g;try{g=JSON.parse(y)}catch{return}if(g?.type!=="result")return;const m=typeof g?.result=="string"?g.result:"";m&&(a=m);const k=String(g?.session_id??"").trim();k&&(l=k)}),o.stderr?.on("data",f=>{c+=typeof f=="string"?f:f.toString("utf8")}),o.on("error",f=>v(!1,f.message)),o.on("close",f=>{if(!u){if(a.trim()){v(!0);return}v(!1,c.trim()||`cursor agent exited with code ${f??"unknown"}`)}})})}createEmptyChat(){const e={...process.env,...this.config.env??{}},t=B(this.config.command,e);return new Promise(n=>{let s;try{s=M(t.command,[...t.prefixArgs,"create-chat"],{cwd:process.cwd(),env:t.env,stdio:["ignore","pipe","pipe"]}).process}catch{n(null);return}let i="",r=!1;const o=setTimeout(()=>{r||(r=!0,this.killChildWithFallback(s),n(null))},3e4);o.unref?.(),s.stdout?.on("data",a=>{i+=typeof a=="string"?a:a.toString("utf8")}),s.on("error",()=>{r||(r=!0,clearTimeout(o),n(null))}),s.on("close",a=>{if(r)return;r=!0,clearTimeout(o);const l=i.trim().split(/\s+/)[0]??"";n(a===0&&/^[0-9a-f-]{16,}$/i.test(l)?l:null)})})}setPermissionHandler(e){this.permissionHandler=e}async ping(e){if(!this.alive||this.stopped||(this.config.options??{}).mcp_tools!==!1&&this.internalApi===null)return!1;for(const n of this.activeBySession.values()){const s=n.child?.pid;if(s&&!T(s))return!1}return!0}getStatus(){let e=0;for(const t of this.pendingBySession.values())e+=t.length;return{alive:this.alive,busy:this.activeBySession.size>0,sessions:this.sessions.size,details:{queueDepth:e+this.inboundQueue.length,activeSessions:this.activeBySession.size}}}getActiveEventIds(){const e=[];for(const t of this.activeBySession.values())t.event.event_id&&e.push(t.event.event_id);return e}clearActiveEventForShutdown(){for(const[e,t]of this.activeBySession)this.snapshotAuditBoundary(t.event.event_id,e);this.activeBySession.clear()}takeAuditBoundary(e){const t=this.completedAuditBoundaries.get(e);return t&&this.completedAuditBoundaries.delete(e),t}getMcpConfig(){return null}async probe(e){const t=this.getStatus();return{...await oe(this.config.command||"cursor-agent",{alive:t.alive,busy:t.busy,started:this.alive},e),session:this.probeSessionRecord()}}probeSessionRecord(){const e=this.firstKnownCursorSessionId(),t=e?V(e):null;if(!t)return{recordPath:null,lastActivityMs:null,freshMs:null};try{const n=N(t);return{recordPath:t,lastActivityMs:n.mtimeMs,freshMs:Date.now()-n.mtimeMs}}catch{return{recordPath:t,lastActivityMs:null,freshMs:null}}}firstKnownCursorSessionId(){for(const e of this.activeBySession.keys()){const t=this.sessionRuntime.get(e);if(t?.cursorSessionId)return t.cursorSessionId}for(const e of this.sessionRuntime.values())if(e.cursorSessionId)return e.cursorSessionId;return null}getUsageSnapshot(e){return this.lastUsageBySession.get(e)??null}getRateLimitsSnapshot(e){const t=this.cachedAccountRateLimits,n=e?this.lastContextBySession.get(e)??null:null;if(!t)return{adapterType:"cursor",available:!1,cached:!1,sampledAt:null,rateLimits:null,contextWindow:n,tokenUsage:null};const s=Date.parse(t.sampledAt);return{adapterType:"cursor",available:t.available&&!!t.rateLimits,cached:!0,sampledAt:Number.isFinite(s)?s:Date.now(),rateLimits:t.rateLimits,contextWindow:n,tokenUsage:null,...t.displayMessage?{displayMessage:t.displayMessage}:{},...t.error?{error:t.error}:{}}}async refreshAccountRateLimits(e){const t=e?.force===!0,n=this.cachedAccountRateLimits,s=n?Date.now()-Date.parse(n.sampledAt):Number.POSITIVE_INFINITY;if(!t&&n&&Number.isFinite(s)&&s<6e4||this.rateLimitsInflight&&(await this.rateLimitsInflight,!t))return;const i=(async()=>{const r=await fe();this.cachedAccountRateLimits={available:r.available,sampledAt:r.sampledAt,rateLimits:r.rateLimits,displayMessage:r.displayMessage,error:r.error},r.available&&r.rateLimits?h.info("cursor-adapter",`[rate-limits] monthly=${r.rateLimits.fiveHour.usedPercentage.toFixed(1)}% api=${r.rateLimits.sevenDay.usedPercentage.toFixed(1)}% resetsAt=${r.rateLimits.fiveHour.resetsAt}`+(r.displayMessage?` msg=${r.displayMessage}`:"")):h.warn("cursor-adapter",`[rate-limits] unavailable: ${r.error??"unknown"}`)})();this.rateLimitsInflight=i.finally(()=>{this.rateLimitsInflight=null}),await this.rateLimitsInflight}getToolbarMeta(e){const t=this.config.options??{},n=e||String(t.aibotSessionId??""),s=n?this.sessionRuntime.get(n):void 0,i=String(s?.modelId||t.model||"auto"),r=C(s?.modeId||t.mode)??"full_auto",o={model_id:i,mode_id:r,currentModelId:i,currentModeId:r,available_models:this.availableModels,available_modes:this.availableModes},a=this.cachedAccountRateLimits?.rateLimits;if(a?.fiveHour||a?.sevenDay){const c=Date.parse(this.cachedAccountRateLimits?.sampledAt??"")||Date.now();o.rate_limits={...a.fiveHour?{fiveHour:a.fiveHour}:{},...a.sevenDay?{sevenDay:a.sevenDay}:{},sampledAt:c}}const l=n?this.lastContextBySession.get(n):void 0;return l&&(o.context_window={usedPercentage:l.usedPercentage,remainingPercentage:l.remainingPercentage,usedTokens:l.usedTokens,sizeTokens:l.sizeTokens}),o}notifyBindingReady(e){if(!this.callbacks.sendUpdateBindingCard)return;const t=this.config.options??{},n=e||String(t.aibotSessionId??"");if(!n)return;const s=this.sessionRuntime.get(n),i=String(s?.cwd||t.workspace||"");i&&this.callbacks.sendUpdateBindingCard(n,this.activeBySession.has(n)?"busy":"ready",i,this.getToolbarMeta(n))}buildPromptText(e){let t;if(!e.contextMessages||e.contextMessages.length===0)t=e.text;else{const i=e.contextMessages.map(r=>`[${r.senderId}] ${r.content}`).join(`
9
+ ${l}`:Q;this.emit("pauseIntake","compaction"),u.info("cursor-adapter",`[compact] summarize resume=${i} session=${e}`);try{const h=await this.runPrintOnce({prompt:c,resumeChatId:i,cwd:r,modelId:o||void 0,modeId:a,timeoutMs:W});if(!h.ok||!h.text.trim())return{status:"failed",message:h.error??"Summarize failed (empty result)"};const p=await this.createEmptyChat();return p?(this.sessionRuntime.set(e,{...n,cwd:r,modelId:o||n.modelId,modeId:a,cursorSessionId:p}),this.transcriptPathBySession.delete(e),this.bindingStore?.setAcpSessionId(e,p),this.identity.forgetSession(e),this.inboundMessageIds.forgetSession(e),this.pendingHandoffBySession.set(e,h.text.trim()),this.lastContextBySession.set(e,Se(0,ve(o))),this.notifyBindingReady(e),u.info("cursor-adapter",`[compact] handoff ready: old=${i} new=${p} summaryChars=${h.text.trim().length}`),{status:"ok",message:"Context compacted via summarize + new chat",data:{previousChatId:i,chatId:p}}):{status:"failed",message:"agent create-chat failed"}}finally{this.emit("resumeIntake","compaction")}}runPrintOnce(e){const t={...process.env,...this.config.env??{}},n=M(this.config.command,t),s=[...n.prefixArgs,...this.config.args??[]];s.push("-p","--output-format","stream-json"),this.config.options?.trust!==!1&&s.push("--trust"),e.cwd&&s.push("--workspace",e.cwd),e.modelId&&s.push("--model",e.modelId);const i=C(e.modeId)??"full_auto";return i==="full_auto"?s.push("--yolo"):i==="plan"&&s.push("--plan"),e.resumeChatId&&s.push("--resume",e.resumeChatId),s.push(e.prompt),new Promise(r=>{let o;try{o=B(n.command,s,{cwd:e.cwd||process.cwd(),env:n.env,stdio:["ignore","pipe","pipe"]}).process}catch(f){r({ok:!1,text:"",error:`spawn failed: ${f instanceof Error?f.message:String(f)}`});return}let a="",l="",c="",h=!1;const p=e.timeoutMs&&e.timeoutMs>0?e.timeoutMs:W,_=setTimeout(()=>{h||(h=!0,this.killChildWithFallback(o),r({ok:!1,text:a,sessionId:l||void 0,error:`compact timeout after ${p}ms`}))},p);_.unref?.();const v=(f,y)=>{h||(h=!0,clearTimeout(_),r({ok:f,text:a,sessionId:l||void 0,error:y}))};q({input:o.stdout}).on("line",f=>{const y=f.trim();if(!y)return;let g;try{g=JSON.parse(y)}catch{return}if(g?.type!=="result")return;const m=typeof g?.result=="string"?g.result:"";m&&(a=m);const w=String(g?.session_id??"").trim();w&&(l=w)}),o.stderr?.on("data",f=>{c+=typeof f=="string"?f:f.toString("utf8")}),o.on("error",f=>v(!1,f.message)),o.on("close",f=>{if(!h){if(a.trim()){v(!0);return}v(!1,c.trim()||`cursor agent exited with code ${f??"unknown"}`)}})})}createEmptyChat(){const e={...process.env,...this.config.env??{}},t=M(this.config.command,e);return new Promise(n=>{let s;try{s=B(t.command,[...t.prefixArgs,"create-chat"],{cwd:process.cwd(),env:t.env,stdio:["ignore","pipe","pipe"]}).process}catch{n(null);return}let i="",r=!1;const o=setTimeout(()=>{r||(r=!0,this.killChildWithFallback(s),n(null))},3e4);o.unref?.(),s.stdout?.on("data",a=>{i+=typeof a=="string"?a:a.toString("utf8")}),s.on("error",()=>{r||(r=!0,clearTimeout(o),n(null))}),s.on("close",a=>{if(r)return;r=!0,clearTimeout(o);const l=i.trim().split(/\s+/)[0]??"";n(a===0&&/^[0-9a-f-]{16,}$/i.test(l)?l:null)})})}setPermissionHandler(e){this.permissionHandler=e}async ping(e){if(!this.alive||this.stopped||(this.config.options??{}).mcp_tools!==!1&&this.internalApi===null)return!1;for(const n of this.activeBySession.values()){const s=n.child?.pid;if(s&&!T(s))return!1}return!0}getStatus(){let e=0;for(const t of this.pendingBySession.values())e+=t.length;return{alive:this.alive,busy:this.activeBySession.size>0,sessions:this.sessions.size,details:{queueDepth:e+this.inboundQueue.length,activeSessions:this.activeBySession.size}}}getActiveEventIds(){const e=[];for(const t of this.activeBySession.values())t.event.event_id&&e.push(t.event.event_id);return e}clearActiveEventForShutdown(){for(const[e,t]of this.activeBySession)this.snapshotAuditBoundary(t.event.event_id,e);this.activeBySession.clear()}takeAuditBoundary(e){const t=this.completedAuditBoundaries.get(e);return t&&this.completedAuditBoundaries.delete(e),t}getMcpConfig(){return null}async probe(e){const t=this.getStatus();return{...await oe(this.config.command||"cursor-agent",{alive:t.alive,busy:t.busy,started:this.alive},e),session:this.probeSessionRecord()}}probeSessionRecord(){const e=this.firstKnownCursorSessionId(),t=e?V(e):null;if(!t)return{recordPath:null,lastActivityMs:null,freshMs:null};try{const n=N(t);return{recordPath:t,lastActivityMs:n.mtimeMs,freshMs:Date.now()-n.mtimeMs}}catch{return{recordPath:t,lastActivityMs:null,freshMs:null}}}firstKnownCursorSessionId(){for(const e of this.activeBySession.keys()){const t=this.sessionRuntime.get(e);if(t?.cursorSessionId)return t.cursorSessionId}for(const e of this.sessionRuntime.values())if(e.cursorSessionId)return e.cursorSessionId;return null}getUsageSnapshot(e){return this.lastUsageBySession.get(e)??null}getRateLimitsSnapshot(e){const t=this.cachedAccountRateLimits,n=e?this.lastContextBySession.get(e)??null:null;if(!t)return{adapterType:"cursor",available:!1,cached:!1,sampledAt:null,rateLimits:null,contextWindow:n,tokenUsage:null};const s=Date.parse(t.sampledAt);return{adapterType:"cursor",available:t.available&&!!t.rateLimits,cached:!0,sampledAt:Number.isFinite(s)?s:Date.now(),rateLimits:t.rateLimits,contextWindow:n,tokenUsage:null,...t.displayMessage?{displayMessage:t.displayMessage}:{},...t.error?{error:t.error}:{}}}async refreshAccountRateLimits(e){const t=e?.force===!0,n=this.cachedAccountRateLimits,s=n?Date.now()-Date.parse(n.sampledAt):Number.POSITIVE_INFINITY;if(!t&&n&&Number.isFinite(s)&&s<6e4||this.rateLimitsInflight&&(await this.rateLimitsInflight,!t))return;const i=(async()=>{const r=await ge();this.cachedAccountRateLimits={available:r.available,sampledAt:r.sampledAt,rateLimits:r.rateLimits,displayMessage:r.displayMessage,error:r.error},r.available&&r.rateLimits?u.info("cursor-adapter",`[rate-limits] monthly=${r.rateLimits.fiveHour.usedPercentage.toFixed(1)}% api=${r.rateLimits.sevenDay.usedPercentage.toFixed(1)}% resetsAt=${r.rateLimits.fiveHour.resetsAt}`+(r.displayMessage?` msg=${r.displayMessage}`:"")):u.warn("cursor-adapter",`[rate-limits] unavailable: ${r.error??"unknown"}`)})();this.rateLimitsInflight=i.finally(()=>{this.rateLimitsInflight=null}),await this.rateLimitsInflight}getToolbarMeta(e){const t=this.config.options??{},n=e||String(t.aibotSessionId??""),s=n?this.sessionRuntime.get(n):void 0,i=String(s?.modelId||t.model||"auto"),r=C(s?.modeId||t.mode)??"full_auto",o={model_id:i,mode_id:r,currentModelId:i,currentModeId:r,available_models:this.availableModels,available_modes:this.availableModes},a=this.cachedAccountRateLimits?.rateLimits;if(a?.fiveHour||a?.sevenDay){const c=Date.parse(this.cachedAccountRateLimits?.sampledAt??"")||Date.now();o.rate_limits={...a.fiveHour?{fiveHour:a.fiveHour}:{},...a.sevenDay?{sevenDay:a.sevenDay}:{},sampledAt:c}}const l=n?this.lastContextBySession.get(n):void 0;return l&&(o.context_window={usedPercentage:l.usedPercentage,remainingPercentage:l.remainingPercentage,usedTokens:l.usedTokens,sizeTokens:l.sizeTokens}),o}notifyBindingReady(e){if(!this.callbacks.sendUpdateBindingCard)return;const t=this.config.options??{},n=e||String(t.aibotSessionId??"");if(!n)return;const s=this.sessionRuntime.get(n),i=String(s?.cwd||t.workspace||"");i&&this.callbacks.sendUpdateBindingCard(n,this.activeBySession.has(n)?"busy":"ready",i,this.getToolbarMeta(n))}buildPromptText(e){let t;if(!e.contextMessages||e.contextMessages.length===0)t=e.text;else{const i=e.contextMessages.map(r=>`[${r.senderId}] ${r.content}`).join(`
10
10
  `);t=R(e.text,`[Conversation context]
11
11
  ${i}`)}const n=this.pendingQuestionAnswerBySession.get(e.adapterSessionId);n&&(this.pendingQuestionAnswerBySession.delete(e.adapterSessionId),t=R(t,`[User answered a previous agent question]
12
12
  ${n}`));const s=this.pendingHandoffBySession.get(e.adapterSessionId);return s&&(this.pendingHandoffBySession.delete(e.adapterSessionId),t=R(t,`[Previous conversation summary \u2014 continue from this handoff]
13
- ${s}`)),this.identity.injectOnce(e.adapterSessionId,t)}handleStdoutLineForActive(e,t){if(e.done)return;const n=t.trim();if(!n)return;e.lastIoAt=Date.now();const{event:s}=e;let i;try{i=JSON.parse(n)}catch{this.armActiveIdleTimer(e.request.adapterSessionId);return}const r=this.auditCaptures.get(s.event_id);if(r&&i&&typeof i=="object"&&r.push({sequence:r.length,observedAt:new Date().toISOString(),event:structuredClone(i)}),i?.type==="tool_call"){const l=String(i?.call_id??""),c=String(i?.subtype??"");l&&(c==="started"||c==="start"?(this.flushAssistantStreamBuffer(e),e.toolCallsInFlight.add(l),e.toolCallsStarted+=1,this.handleSpecialToolCallStarted(e,i)):U.has(c)&&e.toolCallsInFlight.delete(l))}this.armActiveIdleTimer(e.request.adapterSessionId);const o=we(i);o&&this.callbacks.sendRawEventEnvelope?.(s.event_id,s.session_id,o);const a=Ie(i,e.assistantTextEmitted);if(a&&this.enqueueAssistantStreamDelta(e,a),i?.type==="result"){e.sawResult=!0;const l=typeof i?.result=="string"?i.result.trim():"";l&&(e.assistantTextEmitted?l.startsWith(e.assistantTextEmitted)&&l.length>e.assistantTextEmitted.length&&this.enqueueAssistantStreamDelta(e,l.slice(e.assistantTextEmitted.length)):this.enqueueAssistantStreamDelta(e,l)),this.flushAssistantStreamBuffer(e);const c=String(i?.session_id??"").trim();if(c){const m=this.sessionRuntime.get(e.request.adapterSessionId)??{};m.cursorSessionId!==c&&this.transcriptPathBySession.delete(e.request.adapterSessionId),this.sessionRuntime.set(e.request.adapterSessionId,{...m,cursorSessionId:c}),this.bindingStore?.setAcpSessionId(e.request.adapterSessionId,c)}const u=i?.usage??{},p=this.lastUsageBySession.get(e.request.adapterSessionId);this.lastUsageBySession.set(e.request.adapterSessionId,{sampledAt:new Date().toISOString(),turns:(p?.turns??0)+1,total:{input:(p?.total.input??0)+Number(u.inputTokens??0),output:(p?.total.output??0)+Number(u.outputTokens??0),cacheRead:(p?.total.cacheRead??0)+Number(u.cacheReadTokens??0),cacheWrite:(p?.total.cacheWrite??0)+Number(u.cacheWriteTokens??0)}});const _=String((this.sessionRuntime.get(e.request.adapterSessionId)?.modelId??this.config.options?.model??"")||""),v=Math.max(1,(e.toolCallsStarted??0)+1),b=Number(u.inputTokens??0),f=Number(u.cacheReadTokens??0),y=Number(u.cacheWriteTokens??0),g=ge({inputTokens:b,cacheReadTokens:f,cacheWriteTokens:y},_,{apiRounds:v});g&&(this.lastContextBySession.set(e.request.adapterSessionId,g),h.info("cursor-adapter",`[context_window] used=${g.usedTokens} size=${g.sizeTokens} pct=${g.usedPercentage.toFixed(1)}% model=${_||"auto"} rawIn=${b} rawCache=${f} rawWrite=${y} apiRounds=${v}`))}}handleSpecialToolCallStarted(e,t){const n=H(t);if(G(n.toolName)){const s=ke(n.toolInput);s?(e.seq+=1,this.callbacks.sendStreamChunk(e.event.event_id,e.event.session_id,s,e.seq,!1),h.info("cursor-adapter",`CreatePlan forwarded as stream text: event=${e.event.event_id} chars=${s.length}`)):(e.seq+=1,this.callbacks.sendStreamChunk(e.event.event_id,e.event.session_id,"[CreatePlan] \u65B9\u6848\u5185\u5BB9\u89E3\u6790\u5931\u8D25\uFF0C\u672A\u4E0B\u53D1\u6B63\u6587\u3002",e.seq,!1),h.warn("cursor-adapter",`CreatePlan missing plan body: event=${e.event.event_id}`));return}J(n.toolName)&&this.emitAskQuestionCard(e,n.toolInput)}emitAskQuestionCard(e,t){const n=e.event,s=be(t);if(!s){e.seq+=1,this.callbacks.sendStreamChunk(n.event_id,n.session_id,"[AskQuestion] \u95EE\u9898\u5185\u5BB9\u89E3\u6790\u5931\u8D25\uFF0C\u672A\u4E0B\u53D1\u95EE\u9898\u5361\u3002",e.seq,!1),h.warn("cursor-adapter",`AskQuestion parse failed: event=${n.event_id}`);return}if(!this.callbacks.sendAgentQuestionCard){h.warn("cursor-adapter","AskQuestion skipped: sendAgentQuestionCard callback not wired");return}const i=ee(),r=Date.now();this.pendingQuestionByRequestId.set(i,{eventId:n.event_id,sessionId:n.session_id,createdAt:r}),this.callbacks.sendAgentQuestionCard(n.event_id,n.session_id,{request_id:i,mode:"form",questions:s,expires_at:r+x}),h.info("cursor-adapter",`Sent agent_question card: request_id=${i} questions=${s.length} event=${n.event_id}`)}async handleQuestionReply(e){const t=e.params??{},n=String(t.request_id??"").trim();if(!n)return this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"request_id_required","request_id is required"),{handled:!0,kind:"question_reply_no_id"};const s=this.pendingQuestionByRequestId.get(n),i=s?Date.now()-s.createdAt>x+3e4:!0;if(!s||i)return this.pendingQuestionByRequestId.delete(n),this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"interaction_request_not_pending","The question is no longer pending; the reply was not delivered."),{handled:!0,kind:"question_reply_not_pending"};const r=t.resolution??{},o=String(r.type??"");if(o==="action"&&String(r.value??"")==="cancel")return this.pendingQuestionByRequestId.delete(n),this.callbacks.sendLocalActionResult(e.action_id,"ok",{request_id:n,resolution:"cancel"}),{handled:!0,kind:"question_reply_cancel"};let a="";return o==="text"?a=String(r.value??""):o==="map"&&(a=(Array.isArray(r.entries)?r.entries:[]).map(c=>c.value).join(", ")),a?(this.pendingQuestionByRequestId.delete(n),this.pendingQuestionAnswerBySession.set(s.sessionId,a),this.callbacks.sendLocalActionResult(e.action_id,"ok",{request_id:n,resolution:"answer"}),h.info("cursor-adapter",`Question reply cached for next prompt: request_id=${n} session=${s.sessionId}`),{handled:!0,kind:"question_reply"}):(this.pendingQuestionByRequestId.delete(n),this.callbacks.sendLocalActionResult(e.action_id,"ok",{request_id:n,resolution:"cancel"}),{handled:!0,kind:"question_reply_empty"})}enqueueAssistantStreamDelta(e,t){t&&(e.assistantTextEmitted+=t,e.streamBuffer+=t,e.streamBuffer.length>=ve&&this.flushAssistantStreamBuffer(e))}flushAssistantStreamBuffer(e){if(!e.streamBuffer)return;const t=e.streamBuffer;e.streamBuffer="",e.seq+=1,this.callbacks.sendStreamChunk(e.event.event_id,e.event.session_id,t,e.seq,!1,void 0,void 0)}shouldExtendByLiveness(e){const t=this.activeBySession.get(e);if(!t||t.done)return!1;const n=t.child?.pid;if(!n||!T(n)||t.sawResult)return!1;const s=Date.now(),i=s-t.lastIoAt,r=this.transcriptFreshMsForSession(e);if(i<j||r!==null&&r<j)return t.livenessExtendStartAt=s,h.info("cursor-adapter",`Liveness check: fresh progress for ${t.event.event_id} (ioAge=${i}ms, transcriptAge=${r??"n/a"}, toolsInFlight=${t.toolCallsInFlight.size}), extending`),!0;const a=t.livenessExtendStartAt??s;return t.livenessExtendStartAt===void 0&&(t.livenessExtendStartAt=a),s-a>Q?(h.warn("cursor-adapter",`Liveness extension budget exhausted for ${t.event.event_id} (no progress for ${Math.round((s-a)/6e4)}min), allowing idle kill`),!1):(h.info("cursor-adapter",`Liveness check: turn in progress for ${t.event.event_id} (toolsInFlight=${t.toolCallsInFlight.size}, ioAge=${i}ms), extending`),!0)}transcriptFreshMsForSession(e){const t=this.sessionRuntime.get(e)?.cursorSessionId;if(!t)return null;let n=this.transcriptPathBySession.get(e);if(!n){const s=V(t);if(!s)return null;this.transcriptPathBySession.set(e,s),n=s}try{return Date.now()-N(n).mtimeMs}catch{return this.transcriptPathBySession.delete(e),null}}hasPendingQuestionForSession(e){const t=Date.now();for(const[n,s]of this.pendingQuestionByRequestId)if(s.sessionId===e){if(t-s.createdAt>x){this.pendingQuestionByRequestId.delete(n);continue}return!0}return!1}armActiveIdleTimer(e){const t=this.activeBySession.get(e);!t||t.done||(t.idleTimer&&clearTimeout(t.idleTimer),t.idleTimer=setTimeout(()=>{if(t.done)return;if(this.hasPendingQuestionForSession(e)){h.info("cursor-adapter",`Idle timeout skipped: awaiting AskQuestion for ${t.event.event_id}, resetting timer`),this.armActiveIdleTimer(e);return}if(this.shouldExtendByLiveness(e)){this.armActiveIdleTimer(e);return}if(t.sawResult){h.warn("cursor-adapter",`Idle after result with no close \u2014 finishing as responded: event=${t.event.event_id} session=${e}`),this.killChildWithFallback(t.child),this.finishActive(e,"responded");return}const n=t.child?.pid,i=!!(n&&T(n))?`liveness budget exhausted after ${Q/6e4}min`:"child process dead";h.error("cursor-adapter",`Idle timeout (${i}, toolsInFlight=${t.toolCallsInFlight.size}) \u2014 killing cursor child: event=${t.event.event_id} session=${e}`),this.killChildWithFallback(t.child),this.finishActive(e,"failed",`idle timeout: ${i}`)},ye),t.idleTimer.unref?.())}finishActive(e,t,n){const s=this.activeBySession.get(e);if(!s)return;s.done=!0,s.timer&&clearTimeout(s.timer),s.idleTimer&&clearTimeout(s.idleTimer),this.flushAssistantStreamBuffer(s),s.seq+=1;const i=String(s.event.quoted_message_id??"").trim()||void 0;this.callbacks.sendStreamChunk(s.event.event_id,s.event.session_id,"",s.seq,!0,void 0,i),h.info("cursor-adapter",`job finish: event=${s.event.event_id} session=${s.event.session_id} status=${t}${n?` msg=${n}`:""}`),this.snapshotAuditBoundary(s.event.event_id,e),this.emit("eventDone",s.event.event_id),this.callbacks.sendEventResult(s.event.event_id,t,n),t==="responded"?s.handle.emitDone({status:"completed"}):t==="canceled"?s.handle.emitDone({status:"canceled",error:n}):s.handle.emitDone({status:"failed",error:n}),this.activeBySession.delete(e),this.notifyBindingReady(e),this.tryStartNext(e)}snapshotAuditBoundary(e,t){const n=this.auditCaptures.get(e);if(!n)return;const s=this.sessionRuntime.get(t);this.completedAuditBoundaries.set(e,{adapterType:"cursor",providerSessionId:s?.cursorSessionId,cursorEvidence:{events:n}}),this.auditCaptures.delete(e)}shouldRetryWithoutContinue(e){if(!e.usedContinue||e.retryCount>0)return!1;const t=e.stderr.toLowerCase(),n=t.includes("resume")||t.includes("continue")||t.includes("chat")||t.includes("session"),s=t.includes("not found")||t.includes("no previous session")||t.includes("does not exist")||t.includes("invalid");return n&&s}ensureWorkspaceMcpAndSkills(e){if(!this.internalApi)return;this.ensureCursorMcpConfig(e,this.internalApi.url);const t=A(F(),".cursor","skills"),n=ue(t);n.length>0&&h.info("cursor-adapter",`Synced connector skills to ${t}: [${n.join(", ")}]`)}ensureCursorMcpConfig(e,t){const n=de(this.callbacks.getAgentId?.()),s={command:process.execPath,args:[se(ne(import.meta.url),"../../../mcp/acp-mcp-server.js"),"--api-url",t]},i=[e],r=this.resolveGitRoot(e);r&&r!==e&&i.push(r);let o=null;for(const a of i){const l=this.writeCursorMcpConfig(a,n,s,t);!o&&l&&(o=l)}o&&this.recordCursorMcpRegistry(e,o,s)}resolveGitRoot(e){try{return Z("git",["-C",e,"rev-parse","--show-toplevel"],{timeout:5e3,stdio:["ignore","pipe","ignore"],windowsHide:!0}).toString("utf8").trim()||null}catch{return null}}writeCursorMcpConfig(e,t,n,s){try{const i=A(e,".cursor"),r=A(i,"mcp.json");L(i,{recursive:!0});let o={};try{o=JSON.parse(P(r,"utf8"))}catch{o={}}(!o.mcpServers||typeof o.mcpServers!="object")&&(o.mcpServers={});const a=le(o.mcpServers,s);return o.mcpServers[t]=n,D(r,`${JSON.stringify(o,null,2)}
14
- `,"utf8"),h.info("cursor-adapter",`MCP config synced: dir=${e} file=${r} server=${t}${a.length>0?` pruned=${a.join(",")}`:""}`),r}catch(i){return h.warn("cursor-adapter",`Failed to ensure ${A(e,".cursor","mcp.json")}: ${String(i)}`),null}}recordCursorMcpRegistry(e,t,n){try{const s=A(ie.data,"cursor"),i=A(s,"mcp-registry.json");L(s,{recursive:!0});let r={};try{r=JSON.parse(P(i,"utf8"))}catch{r={}}r[e]={mcp_path:t,server:n,updated_at:new Date().toISOString()},D(i,`${JSON.stringify(r,null,2)}
15
- `,"utf8")}catch(s){h.warn("cursor-adapter",`Failed to record MCP registry: ${String(s)}`)}}}export{X as CURSOR_MODE_OPTIONS,$ as CursorAdapter,Ie as extractCursorAssistantStreamDelta,V as findCursorTranscript,ke as formatCursorCreatePlanText,J as isCursorAskQuestionTool,G as isCursorCreatePlanTool,we as mapCursorStreamJsonToRawEnvelope,be as parseCursorAskQuestionPayload};
13
+ ${s}`)),this.identity.injectOnce(e.adapterSessionId,t)}handleStdoutLineForActive(e,t){if(e.done)return;const n=t.trim();if(!n)return;e.lastIoAt=Date.now();const{event:s}=e;let i;try{i=JSON.parse(n)}catch{this.armActiveIdleTimer(e.request.adapterSessionId);return}const r=this.auditCaptures.get(s.event_id);if(r&&i&&typeof i=="object"&&r.push({sequence:r.length,observedAt:new Date().toISOString(),event:structuredClone(i)}),i?.type==="tool_call"){const l=String(i?.call_id??""),c=String(i?.subtype??"");l&&(c==="started"||c==="start"?(this.flushAssistantStreamBuffer(e),e.descendantBaseline===void 0&&this.captureDescendantBaseline(e),e.toolCallsInFlight.add(l),e.toolCallsStarted+=1,this.handleSpecialToolCallStarted(e,i)):U.has(c)&&e.toolCallsInFlight.delete(l))}this.armActiveIdleTimer(e.request.adapterSessionId);const o=Ce(i);o&&this.callbacks.sendRawEventEnvelope?.(s.event_id,s.session_id,o);const a=Re(i,e.assistantTextEmitted);if(a&&this.enqueueAssistantStreamDelta(e,a),i?.type==="result"){e.sawResult=!0;const l=typeof i?.result=="string"?i.result.trim():"";l&&(e.assistantTextEmitted?l.startsWith(e.assistantTextEmitted)&&l.length>e.assistantTextEmitted.length&&this.enqueueAssistantStreamDelta(e,l.slice(e.assistantTextEmitted.length)):this.enqueueAssistantStreamDelta(e,l)),this.flushAssistantStreamBuffer(e);const c=String(i?.session_id??"").trim();if(c){const m=this.sessionRuntime.get(e.request.adapterSessionId)??{};m.cursorSessionId!==c&&this.transcriptPathBySession.delete(e.request.adapterSessionId),this.sessionRuntime.set(e.request.adapterSessionId,{...m,cursorSessionId:c}),this.bindingStore?.setAcpSessionId(e.request.adapterSessionId,c)}const h=i?.usage??{},p=this.lastUsageBySession.get(e.request.adapterSessionId);this.lastUsageBySession.set(e.request.adapterSessionId,{sampledAt:new Date().toISOString(),turns:(p?.turns??0)+1,total:{input:(p?.total.input??0)+Number(h.inputTokens??0),output:(p?.total.output??0)+Number(h.outputTokens??0),cacheRead:(p?.total.cacheRead??0)+Number(h.cacheReadTokens??0),cacheWrite:(p?.total.cacheWrite??0)+Number(h.cacheWriteTokens??0)}});const _=String((this.sessionRuntime.get(e.request.adapterSessionId)?.modelId??this.config.options?.model??"")||""),v=Math.max(1,(e.toolCallsStarted??0)+1),b=Number(h.inputTokens??0),f=Number(h.cacheReadTokens??0),y=Number(h.cacheWriteTokens??0),g=ye({inputTokens:b,cacheReadTokens:f,cacheWriteTokens:y},_,{apiRounds:v});g&&(this.lastContextBySession.set(e.request.adapterSessionId,g),u.info("cursor-adapter",`[context_window] used=${g.usedTokens} size=${g.sizeTokens} pct=${g.usedPercentage.toFixed(1)}% model=${_||"auto"} rawIn=${b} rawCache=${f} rawWrite=${y} apiRounds=${v}`))}}handleSpecialToolCallStarted(e,t){const n=z(t);if(G(n.toolName)){const s=Ie(n.toolInput);s?(e.seq+=1,this.callbacks.sendStreamChunk(e.event.event_id,e.event.session_id,s,e.seq,!1),u.info("cursor-adapter",`CreatePlan forwarded as stream text: event=${e.event.event_id} chars=${s.length}`)):(e.seq+=1,this.callbacks.sendStreamChunk(e.event.event_id,e.event.session_id,"[CreatePlan] \u65B9\u6848\u5185\u5BB9\u89E3\u6790\u5931\u8D25\uFF0C\u672A\u4E0B\u53D1\u6B63\u6587\u3002",e.seq,!1),u.warn("cursor-adapter",`CreatePlan missing plan body: event=${e.event.event_id}`));return}J(n.toolName)&&this.emitAskQuestionCard(e,n.toolInput)}emitAskQuestionCard(e,t){const n=e.event,s=ke(t);if(!s){e.seq+=1,this.callbacks.sendStreamChunk(n.event_id,n.session_id,"[AskQuestion] \u95EE\u9898\u5185\u5BB9\u89E3\u6790\u5931\u8D25\uFF0C\u672A\u4E0B\u53D1\u95EE\u9898\u5361\u3002",e.seq,!1),u.warn("cursor-adapter",`AskQuestion parse failed: event=${n.event_id}`);return}if(!this.callbacks.sendAgentQuestionCard){u.warn("cursor-adapter","AskQuestion skipped: sendAgentQuestionCard callback not wired");return}const i=ee(),r=Date.now();this.pendingQuestionByRequestId.set(i,{eventId:n.event_id,sessionId:n.session_id,createdAt:r}),this.callbacks.sendAgentQuestionCard(n.event_id,n.session_id,{request_id:i,mode:"form",questions:s,expires_at:r+x}),u.info("cursor-adapter",`Sent agent_question card: request_id=${i} questions=${s.length} event=${n.event_id}`)}async handleQuestionReply(e){const t=e.params??{},n=String(t.request_id??"").trim();if(!n)return this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"request_id_required","request_id is required"),{handled:!0,kind:"question_reply_no_id"};const s=this.pendingQuestionByRequestId.get(n),i=s?Date.now()-s.createdAt>x+3e4:!0;if(!s||i)return this.pendingQuestionByRequestId.delete(n),this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"interaction_request_not_pending","The question is no longer pending; the reply was not delivered."),{handled:!0,kind:"question_reply_not_pending"};const r=t.resolution??{},o=String(r.type??"");if(o==="action"&&String(r.value??"")==="cancel")return this.pendingQuestionByRequestId.delete(n),this.callbacks.sendLocalActionResult(e.action_id,"ok",{request_id:n,resolution:"cancel"}),{handled:!0,kind:"question_reply_cancel"};let a="";return o==="text"?a=String(r.value??""):o==="map"&&(a=(Array.isArray(r.entries)?r.entries:[]).map(c=>c.value).join(", ")),a?(this.pendingQuestionByRequestId.delete(n),this.pendingQuestionAnswerBySession.set(s.sessionId,a),this.callbacks.sendLocalActionResult(e.action_id,"ok",{request_id:n,resolution:"answer"}),u.info("cursor-adapter",`Question reply cached for next prompt: request_id=${n} session=${s.sessionId}`),{handled:!0,kind:"question_reply"}):(this.pendingQuestionByRequestId.delete(n),this.callbacks.sendLocalActionResult(e.action_id,"ok",{request_id:n,resolution:"cancel"}),{handled:!0,kind:"question_reply_empty"})}enqueueAssistantStreamDelta(e,t){t&&(e.assistantTextEmitted+=t,e.streamBuffer+=t,e.streamBuffer.length>=Ae&&this.flushAssistantStreamBuffer(e))}flushAssistantStreamBuffer(e){if(!e.streamBuffer)return;const t=e.streamBuffer;e.streamBuffer="",e.seq+=1,this.callbacks.sendStreamChunk(e.event.event_id,e.event.session_id,t,e.seq,!1,void 0,void 0)}captureDescendantBaseline(e){const t=e.child?.pid;if(!t){e.descendantBaseline=null;return}try{e.descendantBaseline=new Set(me(t)),u.info("cursor-adapter",`Liveness baseline captured for ${e.event.event_id}: ${e.descendantBaseline.size} resident descendant(s)`)}catch{e.descendantBaseline=null,u.warn("cursor-adapter",`Liveness baseline capture failed for ${e.event.event_id}; tool-descendant extend disabled`)}}shouldExtendByLiveness(e){const t=this.activeBySession.get(e);if(!t||t.done)return!1;const n=t.child?.pid;if(!n||!T(n)||t.sawResult)return!1;if(t.toolCallsInFlight.size>0&&t.descendantBaseline instanceof Set){let l=0;try{l=he(n,t.descendantBaseline)}catch{l=0}if(l>0)return t.livenessExtendStartAt=void 0,u.info("cursor-adapter",`Liveness check: tool descendants for ${t.event.event_id} (descendants=${l}, toolsInFlight=${t.toolCallsInFlight.size}), extending without budget`),!0}const s=Date.now(),i=s-t.lastIoAt,r=this.transcriptFreshMsForSession(e);if(i<j||r!==null&&r<j)return t.livenessExtendStartAt=s,u.info("cursor-adapter",`Liveness check: fresh progress for ${t.event.event_id} (ioAge=${i}ms, transcriptAge=${r??"n/a"}, toolsInFlight=${t.toolCallsInFlight.size}), extending`),!0;const a=t.livenessExtendStartAt??s;return t.livenessExtendStartAt===void 0&&(t.livenessExtendStartAt=a),s-a>O?(u.warn("cursor-adapter",`Liveness extension budget exhausted for ${t.event.event_id} (no progress for ${Math.round((s-a)/6e4)}min), allowing idle kill`),!1):(u.info("cursor-adapter",`Liveness check: turn in progress for ${t.event.event_id} (toolsInFlight=${t.toolCallsInFlight.size}, ioAge=${i}ms), extending`),!0)}transcriptFreshMsForSession(e){const t=this.sessionRuntime.get(e)?.cursorSessionId;if(!t)return null;let n=this.transcriptPathBySession.get(e);if(!n){const s=V(t);if(!s)return null;this.transcriptPathBySession.set(e,s),n=s}try{return Date.now()-N(n).mtimeMs}catch{return this.transcriptPathBySession.delete(e),null}}hasPendingQuestionForSession(e){const t=Date.now();for(const[n,s]of this.pendingQuestionByRequestId)if(s.sessionId===e){if(t-s.createdAt>x){this.pendingQuestionByRequestId.delete(n);continue}return!0}return!1}armActiveIdleTimer(e){const t=this.activeBySession.get(e);!t||t.done||(t.idleTimer&&clearTimeout(t.idleTimer),t.idleTimer=setTimeout(()=>{if(t.done)return;if(this.hasPendingQuestionForSession(e)){u.info("cursor-adapter",`Idle timeout skipped: awaiting AskQuestion for ${t.event.event_id}, resetting timer`),this.armActiveIdleTimer(e);return}if(this.shouldExtendByLiveness(e)){this.armActiveIdleTimer(e);return}if(t.sawResult){u.warn("cursor-adapter",`Idle after result with no close \u2014 finishing as responded: event=${t.event.event_id} session=${e}`),this.killChildWithFallback(t.child),this.finishActive(e,"responded");return}const n=t.child?.pid,i=!!(n&&T(n))?`liveness budget exhausted after ${O/6e4}min`:"child process dead";u.error("cursor-adapter",`Idle timeout (${i}, toolsInFlight=${t.toolCallsInFlight.size}) \u2014 killing cursor child: event=${t.event.event_id} session=${e}`),this.killChildWithFallback(t.child),this.finishActive(e,"failed",`idle timeout: ${i}`)},_e),t.idleTimer.unref?.())}finishActive(e,t,n){const s=this.activeBySession.get(e);if(!s)return;s.done=!0,s.timer&&clearTimeout(s.timer),s.idleTimer&&clearTimeout(s.idleTimer),this.flushAssistantStreamBuffer(s),s.seq+=1;const i=String(s.event.quoted_message_id??"").trim()||void 0;this.callbacks.sendStreamChunk(s.event.event_id,s.event.session_id,"",s.seq,!0,void 0,i),u.info("cursor-adapter",`job finish: event=${s.event.event_id} session=${s.event.session_id} status=${t}${n?` msg=${n}`:""}`),this.snapshotAuditBoundary(s.event.event_id,e),this.emit("eventDone",s.event.event_id),this.callbacks.sendEventResult(s.event.event_id,t,n),t==="responded"?s.handle.emitDone({status:"completed"}):t==="canceled"?s.handle.emitDone({status:"canceled",error:n}):s.handle.emitDone({status:"failed",error:n}),this.activeBySession.delete(e),this.notifyBindingReady(e),this.tryStartNext(e)}snapshotAuditBoundary(e,t){const n=this.auditCaptures.get(e);if(!n)return;const s=this.sessionRuntime.get(t);this.completedAuditBoundaries.set(e,{adapterType:"cursor",providerSessionId:s?.cursorSessionId,cursorEvidence:{events:n}}),this.auditCaptures.delete(e)}shouldRetryWithoutContinue(e){if(!e.usedContinue||e.retryCount>0)return!1;const t=e.stderr.toLowerCase(),n=t.includes("resume")||t.includes("continue")||t.includes("chat")||t.includes("session"),s=t.includes("not found")||t.includes("no previous session")||t.includes("does not exist")||t.includes("invalid");return n&&s}ensureWorkspaceMcpAndSkills(e){if(!this.internalApi)return;this.ensureCursorMcpConfig(e,this.internalApi.url);const t=A(F(),".cursor","skills"),n=ue(t);n.length>0&&u.info("cursor-adapter",`Synced connector skills to ${t}: [${n.join(", ")}]`)}ensureCursorMcpConfig(e,t){const n=de(this.callbacks.getAgentId?.()),s={command:process.execPath,args:[se(ne(import.meta.url),"../../../mcp/acp-mcp-server.js"),"--api-url",t]},i=[e],r=this.resolveGitRoot(e);r&&r!==e&&i.push(r);let o=null;for(const a of i){const l=this.writeCursorMcpConfig(a,n,s,t);!o&&l&&(o=l)}o&&this.recordCursorMcpRegistry(e,o,s)}resolveGitRoot(e){try{return Z("git",["-C",e,"rev-parse","--show-toplevel"],{timeout:5e3,stdio:["ignore","pipe","ignore"],windowsHide:!0}).toString("utf8").trim()||null}catch{return null}}writeCursorMcpConfig(e,t,n,s){try{const i=A(e,".cursor"),r=A(i,"mcp.json");E(i,{recursive:!0});let o={};try{o=JSON.parse(P(r,"utf8"))}catch{o={}}(!o.mcpServers||typeof o.mcpServers!="object")&&(o.mcpServers={});const a=le(o.mcpServers,s);return o.mcpServers[t]=n,D(r,`${JSON.stringify(o,null,2)}
14
+ `,"utf8"),u.info("cursor-adapter",`MCP config synced: dir=${e} file=${r} server=${t}${a.length>0?` pruned=${a.join(",")}`:""}`),r}catch(i){return u.warn("cursor-adapter",`Failed to ensure ${A(e,".cursor","mcp.json")}: ${String(i)}`),null}}recordCursorMcpRegistry(e,t,n){try{const s=A(ie.data,"cursor"),i=A(s,"mcp-registry.json");E(s,{recursive:!0});let r={};try{r=JSON.parse(P(i,"utf8"))}catch{r={}}r[e]={mcp_path:t,server:n,updated_at:new Date().toISOString()},D(i,`${JSON.stringify(r,null,2)}
15
+ `,"utf8")}catch(s){u.warn("cursor-adapter",`Failed to record MCP registry: ${String(s)}`)}}}export{X as CURSOR_MODE_OPTIONS,$ as CursorAdapter,Re as extractCursorAssistantStreamDelta,V as findCursorTranscript,Ie as formatCursorCreatePlanText,J as isCursorAskQuestionTool,G as isCursorCreatePlanTool,Ce as mapCursorStreamJsonToRawEnvelope,ke as parseCursorAskQuestionPayload};
@@ -0,0 +1 @@
1
+ import{execFileSync as d}from"node:child_process";const g=3e3;function l(n){if(!Number.isFinite(n)||n<=0)return[];if(process.platform==="win32")return[];let e;try{const t=d("ps",["-A","-o","pid=","-o","ppid="],{encoding:"utf8",timeout:g,windowsHide:!0,stdio:["ignore","pipe","pipe"]});e=typeof t=="string"?t:String(t??"")}catch{return[]}return a(n,e)}function m(n){return l(n).length}function b(n,e){try{return l(n).filter(t=>!e.has(t)).length}catch{return 0}}function a(n,e){if(!Number.isFinite(n)||n<=0)return[];const t=new Map;for(const i of e.split(/\r?\n/)){const s=i.trim().split(/\s+/);if(s.length<2)continue;const r=Number(s[0]),c=Number(s[1]);if(!Number.isInteger(r)||r<=0||!Number.isInteger(c)||c<0)continue;const f=t.get(c);f?f.push(r):t.set(c,[r])}const u=[],p=new Set,o=[...t.get(n)??[]];for(;o.length>0;){const i=o.pop();if(i===n||p.has(i))continue;p.add(i),u.push(i);const s=t.get(i);if(s)for(const r of s)o.push(r)}return u}function w(n,e){return a(n,e).length}export{w as countDescendantsInPsTable,m as countLivingDescendants,b as countLivingDescendantsOutsideBaseline,a as listDescendantsInPsTable,l as listLivingDescendants};
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "bridgeVersion": "4.11.6",
4
- "tarball": "grix-dsh-bridge-4.11.6.tgz",
3
+ "bridgeVersion": "4.11.7",
4
+ "tarball": "grix-dsh-bridge-4.11.7.tgz",
5
5
  "size": 23389,
6
6
  "unpackedSize": 94036,
7
- "shasum": "6a2a0da20b9adcd5131ee22961e707941a48f120",
8
- "integrity": "sha512-NF4wMdVAKS1BiqKmK5XYvqKPBmdhrBlpTAV26IUTxiOHcpoOqIS0YCKogGoE83ngqGRmFeOBWkXevHTnP1cLgQ=="
7
+ "shasum": "8638f5f7d7670bd23cbc7049b16e29b71608cce6",
8
+ "integrity": "sha512-LrPNeRaiHtOtwFFN6U4e9gN56ti21OyGcS8x6h83GJdM8gwBq3Jb8+55Wl5GMb6ohRm868/gf8v7EA4ufEsDGw=="
9
9
  }
@@ -1 +1 @@
1
- import{readdir as c,readFile as a,writeFile as o,mkdir as d,unlink as u}from"node:fs/promises";import{join as n}from"node:path";class w{dir;constructor(t){this.dir=t}async ensureDir(){await d(this.dir,{recursive:!0})}async writeRequest(t){await this.ensureDir();const i=n(this.dir,`${t.request_id}.json`);await o(i,JSON.stringify(t,null,2))}async listPending(){await this.ensureDir();try{const t=await c(this.dir),i=[];for(const e of t)if(e.endsWith(".json"))try{const r=await a(n(this.dir,e),"utf8"),s=JSON.parse(r);s.resolved||i.push(s)}catch{}return i.sort((e,r)=>e.created_at-r.created_at)}catch{return[]}}async resolveRequest(t,i){await this.ensureDir();const e=n(this.dir,`${t}.json`);try{const r=await a(e,"utf8"),s=JSON.parse(r);s.resolved=!0,s.resolution=i,await o(e,JSON.stringify(s,null,2))}catch{await o(e,JSON.stringify({request_id:t,resolved:!0,resolution:i,created_at:Date.now()}))}}async cleanResolved(){await this.ensureDir();try{const t=await c(this.dir);for(const i of t)if(i.endsWith(".json"))try{const e=await a(n(this.dir,i),"utf8");JSON.parse(e).resolved&&await u(n(this.dir,i))}catch{}}catch{}}}export{w as PermissionStore};
1
+ import{readdir as o,readFile as a,writeFile as c,mkdir as h,unlink as u}from"node:fs/promises";import{join as r}from"node:path";class w{dir;constructor(t){this.dir=t}async ensureDir(){await h(this.dir,{recursive:!0})}async writeRequest(t){await this.ensureDir();const i=r(this.dir,`${t.request_id}.json`);await c(i,JSON.stringify(t,null,2))}async listPending(){await this.ensureDir();try{const t=await o(this.dir),i=[];for(const e of t)if(e.endsWith(".json"))try{const s=await a(r(this.dir,e),"utf8"),n=JSON.parse(s);n.resolved||i.push(n)}catch{}return i.sort((e,s)=>e.created_at-s.created_at)}catch{return[]}}async getRequest(t){await this.ensureDir();const i=r(this.dir,`${t}.json`);try{const e=await a(i,"utf8");return JSON.parse(e)}catch{return null}}async resolveRequest(t,i){await this.ensureDir();const e=r(this.dir,`${t}.json`);try{const s=await a(e,"utf8"),n=JSON.parse(s);n.resolved=!0,n.resolution=i,await c(e,JSON.stringify(n,null,2))}catch{await c(e,JSON.stringify({request_id:t,resolved:!0,resolution:i,created_at:Date.now()}))}}async removeRequest(t){const i=r(this.dir,`${t}.json`);try{await u(i)}catch{}}async cleanResolved(){await this.ensureDir();try{const t=await o(this.dir);for(const i of t)if(i.endsWith(".json"))try{const e=await a(r(this.dir,i),"utf8");JSON.parse(e).resolved&&await u(r(this.dir,i))}catch{}}catch{}}}export{w as PermissionStore};
@@ -1 +1 @@
1
- function a(e){const t=new Set([`http://127.0.0.1:${e.serverPort}`,`http://localhost:${e.serverPort}`,...e.allowedOrigins]),o=new Set([`127.0.0.1:${e.serverPort}`,`localhost:${e.serverPort}`,...e.allowedHosts]);return{validateRequest(s){const r=i(s,t);if(!r.ok)return r;const n=l(s,o);return n.ok?{ok:!0}:n}}}function i(e,t){const o=e.headers.origin;return o?t.has(o)?{ok:!0}:{ok:!1,statusCode:403,message:`Origin not allowed: ${o}`}:{ok:!0}}function l(e,t){const o=e.headers.host;return o?t.has(o)?{ok:!0}:{ok:!1,statusCode:403,message:`Host not allowed: ${o}`}:{ok:!1,statusCode:403,message:"Missing Host header"}}export{a as createSecurityPolicy};
1
+ function a(t){const o=new Set([`http://127.0.0.1:${t.serverPort}`,`http://localhost:${t.serverPort}`,...t.allowedOrigins]),e=new Set([`127.0.0.1:${t.serverPort}`,`localhost:${t.serverPort}`,...t.allowedHosts]);return{validateRequest(s){const r=i(s,o);if(!r.ok)return r;const n=l(s,e);return n.ok?{ok:!0}:n}}}function i(t,o){const e=t.headers.origin;return e?o.has(e)?{ok:!0}:{ok:!1,statusCode:403,message:`Origin not allowed: ${e}`}:{ok:!0}}function l(t,o){const e=t.headers.host;return e?o.has(e)?{ok:!0}:{ok:!1,statusCode:403,message:`Host not allowed: ${e}`}:{ok:!1,statusCode:403,message:"Missing Host header"}}export{a as createSecurityPolicy};
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import{HookSignalStore as S}from"../core/hooks/hook-signal-store.js";import{resolveRuntimePaths as g}from"../core/config/paths.js";import{resolveLogRotationOptions as y}from"../core/log/rotation.js";import{PermissionStore as v}from"../core/persistence/permission-store.js";import{QuestionStore as k}from"../core/persistence/question-store.js";import{readStdinJSON as A}from"./lib/read-stdin.js";const h=1e3,q=300*1e3;function p(t){const e={hookSpecificOutput:{hookEventName:"PermissionRequest",permissionDecision:t}};process.stdout.write(JSON.stringify(e)+`
3
- `)}function O(t){const e={hookSpecificOutput:{hookEventName:"PermissionRequest",decision:{behavior:"allow",updatedInput:t}}};process.stdout.write(JSON.stringify(e)+`
4
- `)}function _(t){if(t&&typeof t=="object"&&!Array.isArray(t))return t;if(typeof t=="string")try{const e=JSON.parse(t);if(e&&typeof e=="object"&&!Array.isArray(e))return e}catch{return{}}return{}}function R(t,e,o){if(e&&String(e.type??"")==="map"){const d=Array.isArray(e.entries)?e.entries:[],n={};for(const i of d){if(!i||typeof i!="object")continue;const l=i,w=String(l.key??"").trim(),f=String(l.value??"").trim();w&&f&&(n[w]=f)}if(Object.keys(n).length>0)return{...t,answers:n}}const s=(e&&String(e.type??"")==="text"?String(e.value??"").trim():o.trim())||o.trim(),r=Array.isArray(t.questions)?t.questions:[],u={},a=r[0],m=a&&typeof a=="object"?String(a.question??a.header??"question"):"question";return u[m]=s,{...t,answers:u}}async function D(t){const e=g(),o=_(t.tool_input),c=`question-${Date.now()}-${Math.random().toString(36).slice(2,8)}`,s=new k(e.questionRequestsDir),r=Array.isArray(o.questions)?o.questions:[];await s.writeRequest({request_id:c,questions:r,created_at:Date.now(),session_id:String(t.session_id??"").trim(),event_id:String(t.event_id??"").trim()});const u=y(),a=process.env.GRIX_HOOK_SIGNALS_PATH||e.hookSignalsPath;await new S(a,e.hookSignalsLogPath,void 0,u).recordHookEvent(t);const d=Date.now()+q;for(;Date.now()<d;){await new Promise(i=>setTimeout(i,h));const n=await s.getRequest(c);if(n?.resolved){n.resolution==="answer"&&(typeof n.answer=="string"||n.resolution_payload)?O(R(o,n.resolution_payload,String(n.answer??"").trim())):p("deny"),await s.cleanResolved();return}}await s.resolveRequest(c,"cancel"),p("deny")}async function P(){const t=await A(2e3);if(t.hook_event_name!=="PermissionRequest"){process.stdout.write(`{}
5
- `);return}const e=String(t.tool_name??"");if(e==="AskUserQuestion"){await D(t);return}const o=g(),c=_(t.tool_input),s=`perm-${Date.now()}-${Math.random().toString(36).slice(2,8)}`,r=new v(o.permissionRequestsDir);await r.writeRequest({request_id:s,tool_name:e,tool_input:JSON.stringify(c),created_at:Date.now()});const u=y(),a=process.env.GRIX_HOOK_SIGNALS_PATH||o.hookSignalsPath;await new S(a,o.hookSignalsLogPath,void 0,u).recordHookEvent(t);const d=Date.now()+q;for(;Date.now()<d;){await new Promise(l=>setTimeout(l,h));const i=(await r.listPending()).find(l=>l.request_id===s);if(!i||i.resolved){p(i?.resolution==="allow"?"allow":"deny"),await r.cleanResolved();return}}await r.resolveRequest(s,"deny"),p("deny")}P().catch(()=>{p("deny")});
2
+ import{HookSignalStore as g}from"../core/hooks/hook-signal-store.js";import{resolveRuntimePaths as q}from"../core/config/paths.js";import{resolveLogRotationOptions as y}from"../core/log/rotation.js";import{PermissionStore as R}from"../core/persistence/permission-store.js";import{QuestionStore as k}from"../core/persistence/question-store.js";import{readStdinJSON as A}from"./lib/read-stdin.js";const h=1e3,_=300*1e3,w=process.env.GRIX_CLAUDE_SINGLE_TURN==="1";function d(e){const t=w?{hookSpecificOutput:{hookEventName:"PermissionRequest",decision:{behavior:e}}}:{hookSpecificOutput:{hookEventName:"PermissionRequest",permissionDecision:e}};process.stdout.write(JSON.stringify(t)+`
3
+ `)}function O(e){const t={hookSpecificOutput:{hookEventName:"PermissionRequest",decision:{behavior:"allow",updatedInput:e}}};process.stdout.write(JSON.stringify(t)+`
4
+ `)}function v(e){if(e&&typeof e=="object"&&!Array.isArray(e))return e;if(typeof e=="string")try{const t=JSON.parse(e);if(t&&typeof t=="object"&&!Array.isArray(t))return t}catch{return{}}return{}}function N(e,t,i){if(t&&String(t.type??"")==="map"){const p=Array.isArray(t.entries)?t.entries:[],s={};for(const r of p){if(!r||typeof r!="object")continue;const a=r,f=String(a.key??"").trim(),S=String(a.value??"").trim();f&&S&&(s[f]=S)}if(Object.keys(s).length>0)return{...e,answers:s}}const n=(t&&String(t.type??"")==="text"?String(t.value??"").trim():i.trim())||i.trim(),o=Array.isArray(e.questions)?e.questions:[],l={},c=o[0],m=c&&typeof c=="object"?String(c.question??c.header??"question"):"question";return l[m]=n,{...e,answers:l}}async function D(e){const t=q(),i=v(e.tool_input),u=`question-${Date.now()}-${Math.random().toString(36).slice(2,8)}`,n=new k(t.questionRequestsDir),o=Array.isArray(i.questions)?i.questions:[];await n.writeRequest({request_id:u,questions:o,created_at:Date.now(),session_id:String(e.session_id??"").trim(),event_id:String(e.event_id??"").trim()});const l=y(),c=process.env.GRIX_HOOK_SIGNALS_PATH||t.hookSignalsPath;await new g(c,t.hookSignalsLogPath,void 0,l).recordHookEvent(e);const p=Date.now()+_;for(;Date.now()<p;){await new Promise(r=>setTimeout(r,h));const s=await n.getRequest(u);if(s?.resolved){s.resolution==="answer"&&(typeof s.answer=="string"||s.resolution_payload)?O(N(i,s.resolution_payload,String(s.answer??"").trim())):d("deny"),await n.cleanResolved();return}}await n.resolveRequest(u,"cancel"),d("deny")}async function I(){const e=await A(2e3);if(e.hook_event_name!=="PermissionRequest"){process.stdout.write(`{}
5
+ `);return}const t=String(e.tool_name??"");if(t==="AskUserQuestion"){await D(e);return}const i=q(),u=v(e.tool_input),n=`perm-${Date.now()}-${Math.random().toString(36).slice(2,8)}`,o=new R(i.permissionRequestsDir);await o.writeRequest({request_id:n,tool_name:t,tool_input:JSON.stringify(u),created_at:Date.now()});const l=y(),c=process.env.GRIX_HOOK_SIGNALS_PATH||i.hookSignalsPath;await new g(c,i.hookSignalsLogPath,void 0,l).recordHookEvent(e);const p=Date.now()+_;for(;Date.now()<p;){if(await new Promise(a=>setTimeout(a,h)),w){const a=await o.getRequest(n);if(a?.resolved){d(a.resolution==="allow"?"allow":"deny"),await o.removeRequest(n);return}continue}const r=(await o.listPending()).find(a=>a.request_id===n);if(!r||r.resolved){d(r?.resolution==="allow"?"allow":"deny"),await o.cleanResolved();return}}await o.resolveRequest(n,"deny"),w&&await o.removeRequest(n),d("deny")}I().catch(()=>{d("deny")});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grix-connector",
3
- "version": "4.11.6",
3
+ "version": "4.11.7",
4
4
  "description": "Connect local AI coding agents (Claude, Codex, Gemini, Qwen, DeepSeek, Cursor, OpenCode, Pi, OpenHuman, Reasonix) to the Grix scheduling platform. Also serves as an OpenClaw plugin for Grix channel transport.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",