grix-connector 3.9.2 → 3.10.1

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.
@@ -22,4 +22,4 @@ ${I[0]}
22
22
 
23
23
  Waiting for authentication to complete...`))};n.stdout?.on("data",d),n.stderr?.on("data",d);const h=setTimeout(()=>{l||(l=!0,n.kill("SIGKILL"),this.terminalAuthChild=null,s(new Error(`terminal auth timed out after ${N}ms`)))},N);n.on("error",u=>{l||(l=!0,clearTimeout(h),this.terminalAuthChild=null,s(u))}),n.on("exit",u=>{l||(l=!0,clearTimeout(h),this.terminalAuthChild=null,u===0?i():s(new Error(`terminal auth process exited with code ${u}`)))})})}captureAuthUrl(){return new Promise(e=>{if(!this.agentProcess){e(null);return}const t=/https?:\/\/[^\s"')\]]+/;let i=!1;const s=n=>{if(i)return;const a=n.toString().replace(/\x1b\[[0-9;]*m/g,"").match(t);a&&(i=!0,this.agentProcess.removeListener("stderr",s),e(a[0]))};this.agentProcess.on("stderr",s),setTimeout(()=>{i||(i=!0,this.agentProcess.removeListener("stderr",s),e(null))},3e4)})}handleAcpEvent(e){try{o.info("acp-adapter","incoming acp event",{eventType:e.type,sessionId:this.activeRun?.sessionId??null,eventKeys:Object.keys(e),rawEvent:P(e,2e3)})}catch{}if(e.type===f.PermissionRequest){this.activeRun&&this.resetIdleTimer(this.activeRun),this.handlePermissionRequest(e);return}if(e.type===f.ContextWindowUpdate){e.contextWindow&&this.callbacks.onContextWindowUpdated?.(e.contextWindow),this.compacting?this.finishCompaction("context-window-update"):e.contextWindow&&this.maybeScheduleAutoCompact(e.contextWindow);return}const t=this.activeRun;if(!t){this.handleOrphanAcpEvent(e);return}if(t.busyWaiting){this.handleOrphanAcpEvent(e);return}t.responded||(t.responded=!0);let i=!1;switch(e.type){case f.Text:{if(t.lastToolUse=null,t.lastToolResult=null,e.content){i=!0;const s=t.quotedStream.consume(e.content);s.quotedMessageId&&(t.quotedMessageId=s.quotedMessageId),s.deltaContent&&this.appendToStream(t,s.deltaContent)}break}case f.ToolUse:{if(i=!0,t.awaitingToolResult=!0,t.lastToolResult=null,e.toolName){if(e.toolCallId&&(t.toolNamesById.set(e.toolCallId,e.toolName),t.sentToolUseIds.has(e.toolCallId)))break;const s=e.toolInput??"",n=y(s);if(t.lastToolUse?.toolName===e.toolName&&y(t.lastToolUse.toolInput)===n)break;t.lastToolUse={toolName:e.toolName,toolInput:s},e.toolCallId&&t.sentToolUseIds.add(e.toolCallId),this.emitRawEventEnvelope(t,{type:"tool_use",payload:{tool_name:e.toolName,tool_input:s}})||this.callbacks.sendToolUse(t.eventId,t.sessionId,e.toolName,s)}break}case f.ToolResult:{t.awaitingToolResult=!1;{const s=e.toolCallId||e.toolName||"";t.lastToolProgress.delete(s),t.lastToolProgressSentAt.delete(s)}if(t.lastToolUse=null,e.content){i=!0;const s=$(t,e),n=e.content,r=y(n);if(t.lastToolResult?.toolName===s&&y(t.lastToolResult.content)===r)break;t.lastToolResult={toolName:s,content:n},this.emitRawEventEnvelope(t,{type:"tool_result",payload:{tool_name:s,content:n}})||this.callbacks.sendToolResult(t.eventId,t.sessionId,s,n)}break}case f.ToolProgress:{if(i=!0,e.content){const s=e.toolCallId||e.toolName||"";if(t.lastToolProgress.get(s)===e.content)break;t.lastToolProgress.set(s,e.content);const n=Date.now(),r=t.lastToolProgressSentAt.get(s);if(r!==void 0&&n-r<J)break;t.lastToolProgressSentAt.set(s,n);const a=$(t,e);this.emitRawEventEnvelope(t,{type:"tool_progress",payload:{tool_name:a,content:e.content}})||this.callbacks.sendToolResult(t.eventId,t.sessionId,a,e.content)}break}case f.Thinking:{t.lastToolUse=null,t.lastToolResult=null,e.content&&(i=!0,this.emitRawEventEnvelope(t,{type:"thinking",payload:{content:e.content}})||this.callbacks.sendThinking(t.eventId,t.sessionId,e.content));break}case f.Error:{t.lastToolUse=null,t.lastToolResult=null;const s=String(e.error??"unknown error");this.emitRawEventEnvelope(t,{type:"error",payload:{message:s}}),o.error("acp-adapter",`ACP error: ${s}`),this.callbacks.sendRunError(t.eventId,t.sessionId,s);break}case f.Result:{this.emitRawEventEnvelope(t,{type:"result",payload:{done:e.done??!0}});const s=t.quotedStream.flush();s.deltaContent&&this.appendToStream(t,s.deltaContent),s.quotedMessageId&&(t.quotedMessageId=s.quotedMessageId),this.flushStream(),this.finishRun("responded");break}}i&&(t.lastProgressAt=Date.now(),t.idleNoProgressCount=0),this.resetIdleTimer(t)}handlePermissionRequest(e){const t=e.permissionRequest;if(o.info("acp-adapter","permission_request received",{sessionId:this.activeRun?.sessionId,eventId:this.activeRun?.eventId,requestId:e.requestId??null,toolCallId:t?.toolCallId??null,toolName:t?.toolName??null,toolTitle:t?.toolTitle??null,optionsCount:Array.isArray(t?.options)?t.options.length:0,hasActiveRun:this.activeRun!==null,approvalMode:this.approvalMode,rawTransport:this.rawTransport,hasRawEventEnvelopeCb:typeof this.callbacks.sendRawEventEnvelope=="function",hasSendPermissionCardCb:typeof this.callbacks.sendPermissionCard=="function",rawPr:P(t,3e3)}),!t||!e.requestId||!this.acpClient){o.warn("acp-adapter","permission_request dropped: missing prerequisite",{hasPr:t!=null,hasRequestId:!!e.requestId,hasAcpClient:!!this.acpClient});return}if(this.approvalMode==="yolo"||this.approvalMode==="autoEdit"){o.info("acp-adapter",`Auto-approving (${this.approvalMode}): ${t.toolName}`),this.acpClient.respondPermission(t.requestId,{behavior:"allow"}).catch(()=>{});return}const i=t.toolCallId;this.pendingApprovals.set(i,e.requestId);const s=this.activeRun;s?(s.idleTimer&&(clearTimeout(s.idleTimer),s.idleTimer=null),this.emitRawEventEnvelope(s,{type:"permission_request",payload:{request_id:t.requestId,tool_call_id:i,tool_name:t.toolName,tool_title:t.toolTitle,options:t.options}})?o.info("acp-adapter","permission_request forwarded via rawEventEnvelope",{sessionId:s.sessionId,eventId:s.eventId,toolCallId:i,toolName:t.toolName}):(o.info("acp-adapter","permission_request falling back to sendPermissionCard",{sessionId:s.sessionId,eventId:s.eventId,toolCallId:i,toolName:t.toolName}),this.callbacks.sendPermissionCard({eventId:s.eventId,sessionId:s.sessionId,toolCallId:i,toolName:t.toolName,toolTitle:t.toolTitle,options:t.options}))):(o.info("acp-adapter",`Permission request without active run, auto-approving: ${t.toolName}`),this.acpClient.respondPermission(e.requestId,{behavior:"allow"}),this.pendingApprovals.delete(i))}emitRawEventEnvelope(e,t){return!e||!this.rawTransport||!this.callbacks.sendRawEventEnvelope?!1:(this.callbacks.sendRawEventEnvelope(e.eventId,e.sessionId,{type:t.type,payload:t.payload,seq:++this.rawEventSeq,at:new Date().toISOString()}),!0)}resetIdleTimer(e){e.idleTimer&&clearTimeout(e.idleTimer);const t=e.awaitingToolResult?oe:ne,i=e.lastProgressAt+_-Date.now(),s=Math.max(0,Math.min(t,i));e.idleTimer=setTimeout(()=>{if(this.activeRun?.eventId!==e.eventId)return;const n=Date.now()-e.lastProgressAt;if(n>=_){o.error("acp-adapter",`Turn inactive ${Math.round(n/1e3)}s \u2265 cap ${Math.round(_/1e3)}s, forcing timeout: ${e.eventId}`),this.finishRun("failed",`turn inactivity timeout after ${Math.round(n/1e3)}s with no ACP activity`),this.declareStuck();return}const r=this.acpClient;r?.isAlive?r.ping(5e3).then(async a=>{if(this.activeRun?.eventId!==e.eventId)return;if(!a){o.error("acp-adapter",`Ping failed, declaring stuck: ${e.eventId}`),this.finishRun("failed","agent unreachable"),this.declareStuck();return}if(e.awaitingToolResult){o.info("acp-adapter",`Idle timer: tool running, ping ok, continuing: ${e.eventId}`),e.idleNoProgressCount=0,this.resetIdleTimer(e);return}const l=e.lastIdleCheckAt,d=e.lastProgressAt>l,h=d?!1:await Z(r.sessionId,l),u=d||h;e.lastIdleCheckAt=Date.now(),u?(h&&o.info("acp-adapter",`Idle timer: no stream progress but kiro session active, continuing: ${e.eventId}`),e.idleNoProgressCount=0,this.resetIdleTimer(e)):(e.idleNoProgressCount++,e.idleNoProgressCount>=2?(o.error("acp-adapter",`Agent alive but no progress for ${e.idleNoProgressCount} idle cycles, declaring stuck: ${e.eventId}`),this.finishRun("failed",`agent alive but no progress for ${e.idleNoProgressCount} idle cycles`),this.declareStuck()):(o.warn("acp-adapter",`Idle timer fired, ping ok, no progress (${e.idleNoProgressCount}/2): ${e.eventId}`),this.resetIdleTimer(e)))}).catch(()=>{this.activeRun?.eventId===e.eventId&&(o.error("acp-adapter",`Ping error, declaring stuck: ${e.eventId}`),this.finishRun("failed","agent unreachable"),this.declareStuck())}):(o.error("acp-adapter",`Agent idle (no client), declaring stuck: ${e.eventId}`),this.finishRun("failed","agent unreachable"),this.declareStuck())},s)}declareStuck(){this.acpClient&&(this.acpClient.clearSettleTimer(),this.acpClient.removeAllListeners(),this.acpClient=null),this.agentProcess&&(this.agentProcess.close().catch(()=>{}),this.agentProcess=null),this.emit("stuck")}appendToStream(e,t){e.buffer+=t,e.flushTimer||(e.flushTimer=setTimeout(()=>this.flushStream(),se))}emitSegmentedStream(e,t){if(!t)return;const i=e.markdownSegmenter.push(t);for(const s of i)s.text&&(this.callbacks.sendStreamChunk(e.eventId,e.sessionId,s.text,++e.chunkSeq,s.closeAfter===!0,e.currentClientMsgId,e.quotedMessageId),s.closeAfter&&(e.currentSegmentIndex+=1,e.currentClientMsgId=`${e.clientMsgIdBase}_seg_${e.currentSegmentIndex}`,e.chunkSeq=0,o.info("acp-adapter",`stream segment rollover event=${e.eventId} segment=${e.currentSegmentIndex} reason=${s.reason??"threshold"}`)))}handleOrphanAcpEvent(e){if(!(!this.sessionConnected||this.compacting||Date.now()<this.orphanSuppressedUntil)){if(e.type===f.Text&&e.content){this.orphanSessionId===null&&(this.orphanSessionId=this.currentAibotSessionId??"");const t=this.orphanQuotedStream.consume(e.content);t.deltaContent&&(this.orphanBuffer+=t.deltaContent),this.orphanBuffer.length>=ce?this.flushOrphanBuffer():this.scheduleOrphanFlush();return}e.type===f.Result&&this.flushOrphanBuffer()}}scheduleOrphanFlush(){this.orphanFlushTimer&&clearTimeout(this.orphanFlushTimer),this.orphanFlushTimer=setTimeout(()=>{this.orphanFlushTimer=null,this.flushOrphanBuffer()},le),this.orphanFlushTimer.unref?.()}flushOrphanBuffer(){if(this.orphanFlushTimer&&(clearTimeout(this.orphanFlushTimer),this.orphanFlushTimer=null),this.orphanSessionId===null&&!this.orphanBuffer)return;const e=this.orphanQuotedStream.flush();e.deltaContent&&(this.orphanBuffer+=e.deltaContent),this.orphanQuotedStream=new S;const t=this.orphanSessionId||this.currentAibotSessionId;this.orphanSessionId=null;const i=this.orphanBuffer.trim();if(this.orphanBuffer="",!!i){if(!t||!this.callbacks.sendAgentMessage){o.warn("acp-adapter",`orphan agent output dropped (${t?"no sendAgentMessage callback":"no session id"}) chars=${i.length}`);return}o.info("acp-adapter",`delivering orphan agent output session=${t} chars=${i.length}`);for(let s=0;s<i.length;s+=O)this.callbacks.sendAgentMessage(t,i.slice(s,s+O))}}suppressOrphanOutput(e){this.orphanSuppressedUntil=Date.now()+e,this.orphanFlushTimer&&(clearTimeout(this.orphanFlushTimer),this.orphanFlushTimer=null),this.orphanBuffer="",this.orphanQuotedStream=new S,this.orphanSessionId=null}flushStream(){const e=this.activeRun;if(!e||!e.buffer)return;e.flushTimer&&(clearTimeout(e.flushTimer),e.flushTimer=null);const t=e.buffer;e.buffer="",this.emitSegmentedStream(e,t)}finishRun(e,t){const i=this.activeRun;if(!i)return;this.activeRun=null,this.acpClient?.clearSettleTimer(),this.emit("eventDone",i.eventId),this.clearRunTimers(i);const s=i.quotedStream.flush();s.deltaContent&&(i.buffer+=s.deltaContent),s.quotedMessageId&&(i.quotedMessageId=s.quotedMessageId),t&&(e==="failed"?(o.error("acp-adapter",`finishRun failed: ${t} event=${i.eventId}`),i.buffer+=`
24
24
 
25
- The task was interrupted. Please resend your instruction.`):o.info("acp-adapter",`finishRun ${e}: ${t} event=${i.eventId}`)),i.buffer&&(this.emitSegmentedStream(i,i.buffer),i.buffer="");const n=++i.chunkSeq;if(this.callbacks.sendFinalStreamChunkReliable){let a;const l=this.callbacks.sendFinalStreamChunkReliable(i.eventId,i.sessionId,n,i.currentClientMsgId);l.catch(()=>{});const d=new Promise((h,u)=>{a=setTimeout(()=>u(new Error("final chunk ACK timeout")),5e3),a.unref()});Promise.race([l,d]).then(()=>{clearTimeout(a),i.silent||this.callbacks.sendEventResult(i.eventId,e,t),this.persistEventResult(i,e,t),this.replayNextDeferredEvent(),this.tryRunPendingAutoCompact()}).catch(h=>{clearTimeout(a),o.warn("acp-adapter",`finalStreamChunk ACK timeout/failed event=${i.eventId}: ${h instanceof Error?h.message:h}`),this.callbacks.sendStreamChunk(i.eventId,i.sessionId,"",n,!0,i.currentClientMsgId),i.silent||this.callbacks.sendEventResult(i.eventId,e,t),this.persistEventResult(i,e,t),this.replayNextDeferredEvent(),this.tryRunPendingAutoCompact()})}else this.callbacks.sendStreamChunk(i.eventId,i.sessionId,"",n,!0,i.currentClientMsgId),i.silent||this.callbacks.sendEventResult(i.eventId,e,t),this.persistEventResult(i,e,t),this.replayNextDeferredEvent(),this.tryRunPendingAutoCompact()}getContextWindowUsedPercentage(e){return"usedPercentage"in e?Number.isFinite(e.usedPercentage)?e.usedPercentage:null:!Number.isFinite(e.used)||!Number.isFinite(e.size)||e.size<=0?null:Math.min(100,e.used/e.size*100)}maybeScheduleAutoCompact(e){const t=this.getContextWindowUsedPercentage(e);t===null||t<x||this.pendingAutoCompact||this.compacting||(this.pendingAutoCompact=!0,o.info("acp-adapter",`[auto-compact] context_window usedPercentage=${t.toFixed(1)}% >= ${x}%, scheduling compact`),this.tryRunPendingAutoCompact())}tryRunPendingAutoCompact(){this.pendingAutoCompact&&(this.stopped||!this.acpClient?.isAlive||this.activeRun||this.compacting||(this.pendingAutoCompact=!1,this.execCommand("compact","",this.currentAibotSessionId??"").then(e=>{o.info("acp-adapter",`[auto-compact] compact done status=${e.status} msg=${e.message??""}`)}).catch(e=>{o.warn("acp-adapter",`[auto-compact] compact error: ${e instanceof Error?e.message:String(e)}`)})))}persistEventResult(e,t,i){this.eventResults&&!e.silent&&this.eventResults.set({sessionId:e.sessionId,eventId:e.eventId,status:t,msg:i,updatedAt:Date.now()})}}class me extends w{adapterSessionId;constructor(e){super(),this.adapterSessionId=e}emitDone(e){this.emit("done",e)}emitError(e){if(this.listenerCount("error")===0){o.warn("acp-adapter",`Prompt handle error (no listeners): ${e.message}`);return}this.emit("error",e)}async cancel(){}}export{qe as AcpAdapter};
25
+ The task was interrupted. Please resend your instruction.`):o.info("acp-adapter",`finishRun ${e}: ${t} event=${i.eventId}`)),i.buffer&&(this.emitSegmentedStream(i,i.buffer),i.buffer="");const n=++i.chunkSeq;if(this.callbacks.sendFinalStreamChunkReliable){let a;const l=this.callbacks.sendFinalStreamChunkReliable(i.eventId,i.sessionId,i.currentClientMsgId);l.catch(()=>{});const d=new Promise((h,u)=>{a=setTimeout(()=>u(new Error("final chunk ACK timeout")),5e3),a.unref()});Promise.race([l,d]).then(()=>{clearTimeout(a),i.silent||this.callbacks.sendEventResult(i.eventId,e,t),this.persistEventResult(i,e,t),this.replayNextDeferredEvent(),this.tryRunPendingAutoCompact()}).catch(h=>{clearTimeout(a),o.warn("acp-adapter",`finalStreamChunk ACK timeout/failed event=${i.eventId}: ${h instanceof Error?h.message:h}`),this.callbacks.sendStreamChunk(i.eventId,i.sessionId,"",n,!0,i.currentClientMsgId),i.silent||this.callbacks.sendEventResult(i.eventId,e,t),this.persistEventResult(i,e,t),this.replayNextDeferredEvent(),this.tryRunPendingAutoCompact()})}else this.callbacks.sendStreamChunk(i.eventId,i.sessionId,"",n,!0,i.currentClientMsgId),i.silent||this.callbacks.sendEventResult(i.eventId,e,t),this.persistEventResult(i,e,t),this.replayNextDeferredEvent(),this.tryRunPendingAutoCompact()}getContextWindowUsedPercentage(e){return"usedPercentage"in e?Number.isFinite(e.usedPercentage)?e.usedPercentage:null:!Number.isFinite(e.used)||!Number.isFinite(e.size)||e.size<=0?null:Math.min(100,e.used/e.size*100)}maybeScheduleAutoCompact(e){const t=this.getContextWindowUsedPercentage(e);t===null||t<x||this.pendingAutoCompact||this.compacting||(this.pendingAutoCompact=!0,o.info("acp-adapter",`[auto-compact] context_window usedPercentage=${t.toFixed(1)}% >= ${x}%, scheduling compact`),this.tryRunPendingAutoCompact())}tryRunPendingAutoCompact(){this.pendingAutoCompact&&(this.stopped||!this.acpClient?.isAlive||this.activeRun||this.compacting||(this.pendingAutoCompact=!1,this.execCommand("compact","",this.currentAibotSessionId??"").then(e=>{o.info("acp-adapter",`[auto-compact] compact done status=${e.status} msg=${e.message??""}`)}).catch(e=>{o.warn("acp-adapter",`[auto-compact] compact error: ${e instanceof Error?e.message:String(e)}`)})))}persistEventResult(e,t,i){this.eventResults&&!e.silent&&this.eventResults.set({sessionId:e.sessionId,eventId:e.eventId,status:t,msg:i,updatedAt:Date.now()})}}class me extends w{adapterSessionId;constructor(e){super(),this.adapterSessionId=e}emitDone(e){this.emit("done",e)}emitError(e){if(this.listenerCount("error")===0){o.warn("acp-adapter",`Prompt handle error (no listeners): ${e.message}`);return}this.emit("error",e)}async cancel(){}}export{qe as AcpAdapter};
@@ -1,2 +1,2 @@
1
- import{readdirSync as y,readFileSync as g,existsSync as d,statSync as S}from"node:fs";import{dirname as w,join as r,resolve as b}from"node:path";import{homedir as F}from"node:os";import{log as O}from"../../core/log/index.js";function D(t){const e=t.trim();if(!e.startsWith("---"))return{name:"",description:""};const i=e.indexOf("---",3);if(i===-1)return{name:"",description:""};const n=e.slice(3,i).trim();let s="",c="",f;for(const a of n.split(`
2
- `)){const u=a.indexOf(":");if(u===-1)continue;const p=a.slice(0,u).trim();let o=a.slice(u+1).trim();(o.startsWith('"')&&o.endsWith('"')||o.startsWith("'")&&o.endsWith("'"))&&(o=o.slice(1,-1)),p==="name"?s=o:p==="description"?c=o:p==="trigger"&&(f=o)}return{name:s,description:c,trigger:f}}function W(t,e,i){if(!d(t))return[];const n=[];try{for(const s of y(t,{withFileTypes:!0})){const c=r(t,s.name);if(!j(s,c)||s.name.startsWith("."))continue;const f=r(c,"SKILL.md");if(d(f))try{const a=g(f,"utf-8"),u=D(a);u.name&&n.push({name:u.name,description:u.description,trigger:u.trigger,source:e,pluginName:i})}catch{}}}catch{}return n}function l(t,e,i){if(!d(t))return[];const n=i?.maxDepth??6,s=i?.includeHiddenDirs??!1,c=[],f=(a,u)=>{if(u>n)return;let p;try{p=y(a,{withFileTypes:!0,encoding:"utf8"})}catch{return}for(const o of p){const m=r(a,o.name);if(!j(o,m)||!s&&o.name.startsWith("."))continue;const h=r(m,"SKILL.md");if(d(h))try{const x=g(h,"utf-8"),k=D(x);k.name&&c.push({name:k.name,description:k.description,trigger:k.trigger,source:e,pluginName:i?.pluginName})}catch{}f(m,u+1)}};return f(t,0),c}function j(t,e){if(t.isDirectory())return!0;if(!t.isSymbolicLink())return!1;try{return S(e).isDirectory()}catch{return!1}}function L(t){const e=[],i=new Set;for(const n of t){const s=n.name.trim().toLowerCase();i.has(s)||(i.add(s),e.push(n))}return e}function v(t){const e=[],i=new Set;let n=b(t);for(;;){i.has(n)||(e.push(n),i.add(n));const s=w(n);if(s===n)break;n=s}return e}function H(t){const e=r(t,".claude","plugins","installed_plugins.json");if(!d(e))return[];const i=[];try{const n=g(e,"utf-8"),c=JSON.parse(n)?.plugins;if(!c||typeof c!="object")return i;for(const[f,a]of Object.entries(c))if(Array.isArray(a))for(const u of a){const p=u?.installPath;if(!p||!d(p))continue;const o=r(p,"skills"),m=f.split("@")[0],h=W(o,"plugin",m);i.push(...h)}}catch{}return i}function P(t,e){return t==="claude"?"claude":t==="codex"?"codex":t==="pi"?"pi":e==="kiro"?"kiro":"gemini"}function C(t){const e=[],i=t.homeDir??F();switch(t.mode){case"claude":{const s=r(i,".claude","skills");e.push(...l(s,"global")),t.projectDir&&e.push(...l(r(t.projectDir,".claude","skills"),"project")),e.push(...H(i));break}case"codex":{const s=process.env.CODEX_HOME?.trim()||r(i,".codex");if(t.projectDir)for(const c of v(t.projectDir))e.push(...l(r(c,".agents","skills"),"project")),e.push(...l(r(c,".codex","skills"),"project"));e.push(...l(r(i,".agents","skills"),"codex")),e.push(...l(r(s,"skills"),"codex")),e.push(...l(r(s,"skills",".system"),"codex"));break}case"gemini":{e.push(...l(r(i,".gemini","skills"),"gemini")),e.push(...l(r(i,".agents","skills"),"gemini")),t.projectDir&&(e.push(...l(r(t.projectDir,".gemini","skills"),"project")),e.push(...l(r(t.projectDir,".agents","skills"),"project")));break}case"pi":{e.push(...l(r(i,".pi","agent","skills"),"pi")),t.projectDir&&e.push(...l(r(t.projectDir,".pi","skills"),"project"));break}case"kiro":{e.push(...l(r(i,".kiro","skills"),"kiro")),t.projectDir&&e.push(...l(r(t.projectDir,".kiro","skills"),"project"));break}}const n=L(e);return O.info("skill-scanner",`Scanned skills: mode=${t.mode} count=${n.length}`),n}export{L as dedupeSkills,D as parseSkillFrontmatter,P as resolveSkillScanMode,l as scanSkillTree,C as scanSkills};
1
+ import{readdirSync as y,readFileSync as g,existsSync as d,statSync as w}from"node:fs";import{dirname as b,join as r,resolve as F}from"node:path";import{homedir as O}from"node:os";import{log as W}from"../../core/log/index.js";function D(t){const e=t.trim();if(!e.startsWith("---"))return{name:"",description:""};const i=e.indexOf("---",3);if(i===-1)return{name:"",description:""};const n=e.slice(3,i).trim();let s="",c="",a;for(const f of n.split(`
2
+ `)){const u=f.indexOf(":");if(u===-1)continue;const p=f.slice(0,u).trim();let o=f.slice(u+1).trim();(o.startsWith('"')&&o.endsWith('"')||o.startsWith("'")&&o.endsWith("'"))&&(o=o.slice(1,-1)),p==="name"?s=o:p==="description"?c=o:p==="trigger"&&(a=o)}return{name:s,description:c,trigger:a}}function L(t,e,i){if(!d(t))return[];const n=[];try{for(const s of y(t,{withFileTypes:!0})){const c=r(t,s.name);if(!x(s,c)||s.name.startsWith("."))continue;const a=r(c,"SKILL.md");if(d(a))try{const f=g(a,"utf-8"),u=D(f);u.name&&n.push({name:u.name,description:u.description,trigger:u.trigger,source:e,pluginName:i,filePath:a})}catch{}}}catch{}return n}function l(t,e,i){if(!d(t))return[];const n=i?.maxDepth??6,s=i?.includeHiddenDirs??!1,c=[],a=(f,u)=>{if(u>n)return;let p;try{p=y(f,{withFileTypes:!0,encoding:"utf8"})}catch{return}for(const o of p){const m=r(f,o.name);if(!x(o,m)||!s&&o.name.startsWith("."))continue;const h=r(m,"SKILL.md");if(d(h))try{const S=g(h,"utf-8"),k=D(S);k.name&&c.push({name:k.name,description:k.description,trigger:k.trigger,source:e,pluginName:i?.pluginName,filePath:h})}catch{}a(m,u+1)}};return a(t,0),c}function j(t){return t.map(e=>({...e,managed:!0}))}function x(t,e){if(t.isDirectory())return!0;if(!t.isSymbolicLink())return!1;try{return w(e).isDirectory()}catch{return!1}}function P(t){const e=[],i=new Set;for(const n of t){const s=n.name.trim().toLowerCase();i.has(s)||(i.add(s),e.push(n))}return e}function v(t){const e=[],i=new Set;let n=F(t);for(;;){i.has(n)||(e.push(n),i.add(n));const s=b(n);if(s===n)break;n=s}return e}function H(t){const e=r(t,".claude","plugins","installed_plugins.json");if(!d(e))return[];const i=[];try{const n=g(e,"utf-8"),c=JSON.parse(n)?.plugins;if(!c||typeof c!="object")return i;for(const[a,f]of Object.entries(c))if(Array.isArray(f))for(const u of f){const p=u?.installPath;if(!p||!d(p))continue;const o=r(p,"skills"),m=a.split("@")[0],h=j(L(o,"plugin",m));i.push(...h)}}catch{}return i}function M(t,e){return t==="claude"?"claude":t==="codex"?"codex":t==="pi"?"pi":e==="kiro"?"kiro":"gemini"}function C(t){const e=[],i=t.homeDir??O();switch(t.mode){case"claude":{const s=r(i,".claude","skills");e.push(...l(s,"global")),t.projectDir&&e.push(...l(r(t.projectDir,".claude","skills"),"project")),e.push(...H(i));break}case"codex":{const s=process.env.CODEX_HOME?.trim()||r(i,".codex");if(t.projectDir)for(const c of v(t.projectDir))e.push(...l(r(c,".agents","skills"),"project")),e.push(...l(r(c,".codex","skills"),"project"));e.push(...l(r(i,".agents","skills"),"codex")),e.push(...l(r(s,"skills"),"codex")),e.push(...j(l(r(s,"skills",".system"),"codex")));break}case"gemini":{e.push(...l(r(i,".gemini","skills"),"gemini")),e.push(...l(r(i,".agents","skills"),"gemini")),t.projectDir&&(e.push(...l(r(t.projectDir,".gemini","skills"),"project")),e.push(...l(r(t.projectDir,".agents","skills"),"project")));break}case"pi":{e.push(...l(r(i,".pi","agent","skills"),"pi")),t.projectDir&&e.push(...l(r(t.projectDir,".pi","skills"),"project"));break}case"kiro":{e.push(...l(r(i,".kiro","skills"),"kiro")),t.projectDir&&e.push(...l(r(t.projectDir,".kiro","skills"),"project"));break}}const n=P(e);return W.info("skill-scanner",`Scanned skills: mode=${t.mode} count=${n.length}`),n}export{P as dedupeSkills,j as markManaged,D as parseSkillFrontmatter,M as resolveSkillScanMode,l as scanSkillTree,C as scanSkills};
@@ -1,8 +1,8 @@
1
- import{createInterface as q}from"node:readline";import{EventEmitter as M}from"node:events";import{mkdirSync as x,readFileSync as T,writeFileSync as C,readdirSync as j,statSync as F}from"node:fs";import{join as f,resolve as N}from"node:path";import{fileURLToPath as U}from"node:url";import{homedir as $}from"node:os";import{GRIX_PATHS as H,log as c}from"../../core/log/index.js";import{IdentityInjector as O}from"../shared/identity-injector.js";import{buildSimpleProbeReport as W}from"../shared/probe-util.js";import{InternalApiServer as J}from"../../core/mcp/internal-api-server.js";import{syncDefaultSkillsToDir as G}from"../../default-skills/index.js";import{resolveCommandPath as B,spawnCommand as E,killProcessGroup as S}from"../../core/runtime/spawn.js";import{SessionBindingStore as X}from"../../core/persistence/session-binding-store.js";const b=12e4,z=new Set(["plan","ask"]);function K(h){try{return process.kill(h,0),!0}catch(e){return e.code==="EPERM"}}function L(h,e,t){if(t<0)return null;let i;try{i=j(h,{withFileTypes:!0})}catch{return null}for(const s of i){const o=f(h,s.name);if(s.isDirectory()){const n=L(o,e,t-1);if(n)return n}else if(s.isFile()&&s.name.endsWith(e))return o}return null}function Q(h,e){if(!h)return null;const t=e||f($(),".cursor","projects");return L(t,`${h}.jsonl`,6)}class P extends M{adapterSessionId;constructor(e){super(),this.adapterSessionId=e}emitDone(e){this.emit("done",e)}emitError(e){if(this.listenerCount("error")===0){c.warn("cursor-adapter",`Prompt handle error (no listeners): ${e.message}`);return}this.emit("error",e)}async cancel(){}}class I extends M{type="cursor";static STDERR_MAX_CHARS=8192;config;callbacks;alive=!1;stopped=!1;permissionHandler=null;internalApi=null;bindingStore=null;identity;inboundQueue=[];pendingBySession=new Map;sessions=new Set;sessionRuntime=new Map;sessionSeq=0;lastUsageBySession=new Map;_availableModels=[];activeBySession=new Map;constructor(e,t){super(),this.config=e,this.callbacks=t;const i=e.options??{};this.bindingStore=i.bindingStore instanceof X?i.bindingStore:null,this.identity=new O("cursor-adapter",t.getAgentProfile)}async start(){this.alive=!0,this.stopped=!1,(this.config.options??{}).mcp_tools!==!1&&(this.internalApi=new J,this.internalApi.setInvokeHandler(async(t,i,s,o)=>this.callbacks.agentInvoke(t,i,o)),await this.internalApi.start()),this.refreshModels().catch(()=>{})}get availableModels(){return this._availableModels}async refreshModels(){try{const e={...process.env,...this.config.env??{}},t=B(this.config.command,typeof e.PATH=="string"?e.PATH:void 0),i=E(t,["models"],{cwd:process.cwd(),env:e,stdio:["ignore","pipe","pipe"]}),s=await new Promise(n=>{let r="";i.process.stdout?.on("data",d=>{r+=String(d)}),i.process.once("close",()=>n(r))}),o=[];for(const n of s.split(`
2
- `)){const r=n.trim();if(!r||r.toLowerCase().includes("available model"))continue;const d=r.match(/^(\S+)\s+-\s+(.+)$/);d&&o.push({id:d[1],displayName:d[2].trim()})}o.length>0&&(this._availableModels=o,c.info("cursor-adapter",`Loaded ${o.length} available models`))}catch(e){c.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())S(e.child,"SIGTERM");this.activeBySession.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 i=this.sessionRuntime.get(e)??{};this.sessionRuntime.set(e,{...i,cwd:typeof t?.cwd=="string"?t.cwd:i.cwd,modelId:typeof t?.modelId=="string"?t.modelId:i.modelId,modeId:typeof t?.modeId=="string"?t.modeId:i.modeId})}onAgentProfileChanged(){this.identity.onProfileChanged()}async destroySession(e){this.sessions.delete(e),this.sessionRuntime.delete(e),this.identity.forgetSession(e)}sendPrompt(e){const t=new P(e.adapterSessionId);if(!this.alive||this.stopped)return queueMicrotask(()=>t.emitError(new Error("adapter not running"))),t;const i=this.inboundQueue.shift()??{event_id:`cursor-evt-${Date.now()}`,session_id:e.adapterSessionId,content:e.text},s=e.adapterSessionId,o=this.pendingBySession.get(s)??[];return o.push({event:i,request:e,handle:t}),this.pendingBySession.set(s,o),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 i=t.shift();t.length===0?this.pendingBySession.delete(e):this.pendingBySession.set(e,t),i&&this.startJob(i,!1,0)}startJob(e,t,i){const{event:s,request:o,handle:n}=e,r=o.adapterSessionId,d=this.sessionRuntime.get(o.adapterSessionId)??{},m=this.config.options??{},u=[...this.config.args??[]];u.push("-p","--output-format","stream-json","--stream-partial-output"),m.trust!==!1&&u.push("--trust");const v=d.cwd||m.workspace;this.internalApi&&v&&(this.ensureWorkspaceMcpAndSkills(v),u.push("--approve-mcps")),v&&u.push("--workspace",v);const A=d.modelId||m.model;A&&u.push("--model",A);const y=d.modeId||m.mode;y&&z.has(y)&&u.push("--mode",y);const w=d.cursorSessionId??this.bindingStore?.getAcpSessionId(o.adapterSessionId),R=!!(w&&m.use_continue!==!1&&!t);R&&u.push("--resume",w),u.push(this.buildPromptText(o));const _={...process.env,...this.config.env??{}},D=B(this.config.command,typeof _.PATH=="string"?_.PATH:void 0);c.info("cursor-adapter",`job start: event=${s.event_id} session=${s.session_id} retry=${i}`);let p;try{p=E(D,u,{cwd:v||process.cwd(),env:_,stdio:["ignore","pipe","pipe"]}).process}catch(a){this.finishActive(r,"failed",`spawn failed: ${a instanceof Error?a.message:String(a)}`);return}this.callbacks.sendEventAck(s.event_id,s.session_id);const l={event:s,request:o,handle:n,child:p,seq:0,done:!1,stderr:"",timer:null,idleTimer:null,retryCount:i,usedContinue:R,workspace:v||process.cwd(),args:u};this.activeBySession.set(r,l),this.armActiveIdleTimer(r);const k=o.timeoutMs&&o.timeoutMs>0?o.timeoutMs:0;k>0&&(l.timer=setTimeout(()=>{l.done||(S(p,"SIGTERM"),this.finishActive(r,"failed",`cursor agent timeout after ${k}ms`))},k)),q({input:p.stdout}).on("line",a=>this.handleStdoutLineForActive(l,a)),p.stderr?.on("data",a=>{const g=l.stderr+String(a??"");l.stderr=g.length>I.STDERR_MAX_CHARS?g.slice(-I.STDERR_MAX_CHARS):g}),p.once("error",a=>{this.finishActive(r,"failed",`spawn failed: ${String(a?.message??a)}`)}),p.once("close",a=>{if(!l.done)if((a??0)===0)this.finishActive(r,"responded");else{if(this.shouldRetryWithoutContinue(l)){l.timer&&clearTimeout(l.timer),l.idleTimer&&clearTimeout(l.idleTimer),this.activeBySession.delete(r),this.startJob(e,!0,l.retryCount+1);return}const g=l.stderr.trim()||`cursor agent exited with code ${a??-1}`;this.finishActive(r,"failed",g)}})}async cancel(e){const t=this.activeBySession.get(e);t&&(S(t.child,"SIGTERM"),this.finishActive(e,"canceled","canceled"))}deliverInboundEvent(e){const t=String(e.session_id??"").trim();if(!t){c.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){c.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 i={adapterSessionId:t,text:String(e.content??"")},s=new P(t),o=this.pendingBySession.get(t)??[];o.push({event:e,request:i,handle:s}),this.pendingBySession.set(t,o),c.info("cursor-adapter",`inbound queued: event=${e.event_id} session=${t} depth=${o.length}`),this.tryStartNext(t)}killChildWithFallback(e){S(e,"SIGTERM"),setTimeout(()=>{if(e.exitCode===null&&e.signalCode===null)try{S(e,"SIGKILL")}catch{}},5e3).unref()}deliverStopEvent(e,t){if(t){const i=this.activeBySession.get(t);if(i?.event.event_id===e){this.killChildWithFallback(i.child),this.finishActive(t,"canceled","stopped");return}const s=this.pendingBySession.get(t)??[],o=s.findIndex(n=>n.event.event_id===e);if(o>=0){const[n]=s.splice(o,1);s.length===0?this.pendingBySession.delete(t):this.pendingBySession.set(t,s),this.callbacks.sendEventAck(n.event.event_id,n.event.session_id),this.callbacks.sendEventResult(n.event.event_id,"canceled","stopped"),n.handle.emitDone({status:"canceled",error:"stopped"})}return}for(const[i,s]of this.activeBySession.entries())if(s.event.event_id===e){this.killChildWithFallback(s.child),this.finishActive(i,"canceled","stopped");return}for(const[i,s]of this.pendingBySession.entries()){const o=s.findIndex(r=>r.event.event_id===e);if(o<0)continue;const[n]=s.splice(o,1);s.length===0?this.pendingBySession.delete(i):this.pendingBySession.set(i,s),this.callbacks.sendEventAck(n.event.event_id,n.event.session_id),this.callbacks.sendEventResult(n.event.event_id,"canceled","stopped"),n.handle.emitDone({status:"canceled",error:"stopped"});return}}async handleLocalAction(e){const t=String(e.action_type??"").trim().toLowerCase(),i=e.params??{},s=String(i.session_id??"").trim(),o=e.action_id;switch(t){case"set_model":{const n=String(i.model_id??"").trim();if(!n||!s)return this.callbacks.sendLocalActionResult(o,"failed",void 0,"invalid_params","model_id and session_id are required"),{handled:!0,kind:"set_model"};const r=this.sessionRuntime.get(s)??{};return this.sessionRuntime.set(s,{...r,modelId:n}),this.callbacks.sendLocalActionResult(o,"ok",{outcome:"model_set",session_context:{model_id:n,mode_id:r.modeId??null,modelId:n,modeId:r.modeId??null},model_id:n,mode_id:r.modeId??null,available_models:this.availableModels}),{handled:!0,kind:"set_model"}}case"set_mode":{const n=String(i.mode_id??"").trim();if(!n||!s)return this.callbacks.sendLocalActionResult(o,"failed",void 0,"invalid_params","mode_id and session_id are required"),{handled:!0,kind:"set_mode"};const r=this.sessionRuntime.get(s)??{};return this.sessionRuntime.set(s,{...r,modeId:n}),this.callbacks.sendLocalActionResult(o,"ok",{outcome:"mode_set",session_context:{model_id:r.modelId??null,mode_id:n,modelId:r.modelId??null,modeId:n},model_id:r.modelId??null,mode_id:n}),{handled:!0,kind:"set_mode"}}case"get_context":{const n=s?this.sessionRuntime.get(s):void 0;return this.callbacks.sendLocalActionResult(o,"ok",{session_context:{model_id:n?.modelId??null,mode_id:n?.modeId??null,cwd:n?.cwd??null},model_id:n?.modelId??null,mode_id:n?.modeId??null,modelId:n?.modelId??null,modeId:n?.modeId??null,cwd:n?.cwd??null,available_models:this.availableModels}),{handled:!0,kind:"get_context"}}case"get_rate_limits":{const n=this.getRateLimitsSnapshot();return this.callbacks.sendLocalActionResult(o,"ok",n),{handled:!0,kind:"get_rate_limits"}}case"get_session_usage":{const n=s?this.getUsageSnapshot(s):null;return n?this.callbacks.sendLocalActionResult(o,"ok",{adapterType:"cursor",available:!0,sampledAt:n.sampledAt,turns:n.turns,tokenUsage:n.total}):this.callbacks.sendLocalActionResult(o,"ok",{adapterType:"cursor",available:!1,sampledAt:null,turns:0,tokenUsage:null}),{handled:!0,kind:"get_session_usage"}}case"session_control":{const n=String(i.verb??"").trim().toLowerCase();if(n==="restart"&&s)this.deliverStopEvent("__all__",s),this.callbacks.sendLocalActionResult(o,"ok",{outcome:"restarted"});else if(n==="status"){const r=this.sessionRuntime.get(s),d=this.activeBySession.has(s);this.callbacks.sendLocalActionResult(o,"ok",{verb:"status",status:d?"running":"idle",session_context:{model_id:r?.modelId??null,mode_id:r?.modeId??null,cwd:r?.cwd??null},model_id:r?.modelId??null,mode_id:r?.modeId??null,modelId:r?.modelId??null,modeId:r?.modeId??null,cwd:r?.cwd??null})}else this.callbacks.sendLocalActionResult(o,"failed",void 0,"invalid_verb",`Unsupported verb: ${n}`);return{handled:!0,kind:"session_control"}}default:return{handled:!1,kind:"unsupported"}}}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 i of this.activeBySession.values()){const s=i.child?.pid;if(s&&!K(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(){this.activeBySession.clear()}getMcpConfig(){return null}async probe(e){const t=this.getStatus();return{...await W(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?Q(e):null;if(!t)return{recordPath:null,lastActivityMs:null,freshMs:null};try{const i=F(t);return{recordPath:t,lastActivityMs:i.mtimeMs,freshMs:Date.now()-i.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(){return{adapterType:"cursor",available:!1,cached:!1,sampledAt:null,rateLimits:null,contextWindow:null,tokenUsage:null}}buildPromptText(e){let t;return!e.contextMessages||e.contextMessages.length===0?t=e.text:t=`${e.contextMessages.map(s=>`[${s.senderId}] ${s.content}`).join(`
1
+ import{createInterface as D}from"node:readline";import{EventEmitter as R}from"node:events";import{mkdirSync as T,readFileSync as M,writeFileSync as C,readdirSync as F,statSync as q}from"node:fs";import{join as f,resolve as O}from"node:path";import{fileURLToPath as j}from"node:url";import{homedir as x}from"node:os";import{GRIX_PATHS as U,log as c}from"../../core/log/index.js";import{IdentityInjector as N}from"../shared/identity-injector.js";import{buildSimpleProbeReport as H}from"../shared/probe-util.js";import{InternalApiServer as W}from"../../core/mcp/internal-api-server.js";import{syncDefaultSkillsToDir as J}from"../../default-skills/index.js";import{resolveCommandPath as E,spawnCommand as $,killProcessGroup as S}from"../../core/runtime/spawn.js";import{SessionBindingStore as G}from"../../core/persistence/session-binding-store.js";const z=12e4,X=6e5,K=new Set(["completed","failed","error","errored","canceled","cancelled","rejected"]),Q=new Set(["plan","ask"]);function Y(h){try{return process.kill(h,0),!0}catch(e){return e.code==="EPERM"}}function B(h,e,t){if(t<0)return null;let i;try{i=F(h,{withFileTypes:!0})}catch{return null}for(const s of i){const n=f(h,s.name);if(s.isDirectory()){const o=B(n,e,t-1);if(o)return o}else if(s.isFile()&&s.name.endsWith(e))return n}return null}function V(h,e){if(!h)return null;const t=e||f(x(),".cursor","projects");return B(t,`${h}.jsonl`,6)}class L extends R{adapterSessionId;constructor(e){super(),this.adapterSessionId=e}emitDone(e){this.emit("done",e)}emitError(e){if(this.listenerCount("error")===0){c.warn("cursor-adapter",`Prompt handle error (no listeners): ${e.message}`);return}this.emit("error",e)}async cancel(){}}class b extends R{type="cursor";static STDERR_MAX_CHARS=8192;config;callbacks;alive=!1;stopped=!1;permissionHandler=null;internalApi=null;bindingStore=null;identity;inboundQueue=[];pendingBySession=new Map;sessions=new Set;sessionRuntime=new Map;sessionSeq=0;lastUsageBySession=new Map;_availableModels=[];activeBySession=new Map;constructor(e,t){super(),this.config=e,this.callbacks=t;const i=e.options??{};this.bindingStore=i.bindingStore instanceof G?i.bindingStore:null,this.identity=new N("cursor-adapter",t.getAgentProfile)}async start(){this.alive=!0,this.stopped=!1,(this.config.options??{}).mcp_tools!==!1&&(this.internalApi=new W,this.internalApi.setInvokeHandler(async(t,i,s,n)=>this.callbacks.agentInvoke(t,i,n)),await this.internalApi.start()),this.refreshModels().catch(()=>{})}get availableModels(){return this._availableModels}async refreshModels(){try{const e={...process.env,...this.config.env??{}},t=E(this.config.command,typeof e.PATH=="string"?e.PATH:void 0),i=$(t,["models"],{cwd:process.cwd(),env:e,stdio:["ignore","pipe","pipe"]}),s=await new Promise(o=>{let r="";i.process.stdout?.on("data",d=>{r+=String(d)}),i.process.once("close",()=>o(r))}),n=[];for(const o of s.split(`
2
+ `)){const r=o.trim();if(!r||r.toLowerCase().includes("available model"))continue;const d=r.match(/^(\S+)\s+-\s+(.+)$/);d&&n.push({id:d[1],displayName:d[2].trim()})}n.length>0&&(this._availableModels=n,c.info("cursor-adapter",`Loaded ${n.length} available models`))}catch(e){c.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())S(e.child,"SIGTERM");this.activeBySession.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 i=this.sessionRuntime.get(e)??{};this.sessionRuntime.set(e,{...i,cwd:typeof t?.cwd=="string"?t.cwd:i.cwd,modelId:typeof t?.modelId=="string"?t.modelId:i.modelId,modeId:typeof t?.modeId=="string"?t.modeId:i.modeId})}onAgentProfileChanged(){this.identity.onProfileChanged()}async destroySession(e){this.sessions.delete(e),this.sessionRuntime.delete(e),this.identity.forgetSession(e)}sendPrompt(e){const t=new L(e.adapterSessionId);if(!this.alive||this.stopped)return queueMicrotask(()=>t.emitError(new Error("adapter not running"))),t;const i=this.inboundQueue.shift()??{event_id:`cursor-evt-${Date.now()}`,session_id:e.adapterSessionId,content:e.text},s=e.adapterSessionId,n=this.pendingBySession.get(s)??[];return n.push({event:i,request:e,handle:t}),this.pendingBySession.set(s,n),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 i=t.shift();t.length===0?this.pendingBySession.delete(e):this.pendingBySession.set(e,t),i&&this.startJob(i,!1,0)}startJob(e,t,i){const{event:s,request:n,handle:o}=e,r=n.adapterSessionId,d=this.sessionRuntime.get(n.adapterSessionId)??{},m=this.config.options??{},u=[...this.config.args??[]];u.push("-p","--output-format","stream-json","--stream-partial-output"),m.trust!==!1&&u.push("--trust");const g=d.cwd||m.workspace;this.internalApi&&g&&(this.ensureWorkspaceMcpAndSkills(g),u.push("--approve-mcps")),g&&u.push("--workspace",g);const k=d.modelId||m.model;k&&u.push("--model",k);const _=d.modeId||m.mode;_&&Q.has(_)&&u.push("--mode",_);const A=d.cursorSessionId??this.bindingStore?.getAcpSessionId(n.adapterSessionId),w=!!(A&&m.use_continue!==!1&&!t);w&&u.push("--resume",A),u.push(this.buildPromptText(n));const y={...process.env,...this.config.env??{}},P=E(this.config.command,typeof y.PATH=="string"?y.PATH:void 0);c.info("cursor-adapter",`job start: event=${s.event_id} session=${s.session_id} retry=${i}`);let p;try{p=$(P,u,{cwd:g||process.cwd(),env:y,stdio:["ignore","pipe","pipe"]}).process}catch(a){this.finishActive(r,"failed",`spawn failed: ${a instanceof Error?a.message:String(a)}`);return}this.callbacks.sendEventAck(s.event_id,s.session_id);const l={event:s,request:n,handle:o,child:p,seq:0,done:!1,stderr:"",timer:null,idleTimer:null,toolCallsInFlight:new Set,retryCount:i,usedContinue:w,workspace:g||process.cwd(),args:u};this.activeBySession.set(r,l),this.armActiveIdleTimer(r);const I=n.timeoutMs&&n.timeoutMs>0?n.timeoutMs:0;I>0&&(l.timer=setTimeout(()=>{l.done||(S(p,"SIGTERM"),this.finishActive(r,"failed",`cursor agent timeout after ${I}ms`))},I)),D({input:p.stdout}).on("line",a=>this.handleStdoutLineForActive(l,a)),p.stderr?.on("data",a=>{const v=l.stderr+String(a??"");l.stderr=v.length>b.STDERR_MAX_CHARS?v.slice(-b.STDERR_MAX_CHARS):v}),p.once("error",a=>{this.finishActive(r,"failed",`spawn failed: ${String(a?.message??a)}`)}),p.once("close",a=>{if(!l.done)if((a??0)===0)this.finishActive(r,"responded");else{if(this.shouldRetryWithoutContinue(l)){l.timer&&clearTimeout(l.timer),l.idleTimer&&clearTimeout(l.idleTimer),this.activeBySession.delete(r),this.startJob(e,!0,l.retryCount+1);return}const v=l.stderr.trim()||`cursor agent exited with code ${a??-1}`;this.finishActive(r,"failed",v)}})}async cancel(e){const t=this.activeBySession.get(e);t&&(S(t.child,"SIGTERM"),this.finishActive(e,"canceled","canceled"))}deliverInboundEvent(e){const t=String(e.session_id??"").trim();if(!t){c.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){c.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 i={adapterSessionId:t,text:String(e.content??"")},s=new L(t),n=this.pendingBySession.get(t)??[];n.push({event:e,request:i,handle:s}),this.pendingBySession.set(t,n),c.info("cursor-adapter",`inbound queued: event=${e.event_id} session=${t} depth=${n.length}`),this.tryStartNext(t)}killChildWithFallback(e){S(e,"SIGTERM"),setTimeout(()=>{if(e.exitCode===null&&e.signalCode===null)try{S(e,"SIGKILL")}catch{}},5e3).unref()}deliverStopEvent(e,t){if(t){const i=this.activeBySession.get(t);if(i?.event.event_id===e){this.killChildWithFallback(i.child),this.finishActive(t,"canceled","stopped");return}const s=this.pendingBySession.get(t)??[],n=s.findIndex(o=>o.event.event_id===e);if(n>=0){const[o]=s.splice(n,1);s.length===0?this.pendingBySession.delete(t):this.pendingBySession.set(t,s),this.callbacks.sendEventAck(o.event.event_id,o.event.session_id),this.callbacks.sendEventResult(o.event.event_id,"canceled","stopped"),o.handle.emitDone({status:"canceled",error:"stopped"})}return}for(const[i,s]of this.activeBySession.entries())if(s.event.event_id===e){this.killChildWithFallback(s.child),this.finishActive(i,"canceled","stopped");return}for(const[i,s]of this.pendingBySession.entries()){const n=s.findIndex(r=>r.event.event_id===e);if(n<0)continue;const[o]=s.splice(n,1);s.length===0?this.pendingBySession.delete(i):this.pendingBySession.set(i,s),this.callbacks.sendEventAck(o.event.event_id,o.event.session_id),this.callbacks.sendEventResult(o.event.event_id,"canceled","stopped"),o.handle.emitDone({status:"canceled",error:"stopped"});return}}async handleLocalAction(e){const t=String(e.action_type??"").trim().toLowerCase(),i=e.params??{},s=String(i.session_id??"").trim(),n=e.action_id;switch(t){case"set_model":{const o=String(i.model_id??"").trim();if(!o||!s)return this.callbacks.sendLocalActionResult(n,"failed",void 0,"invalid_params","model_id and session_id are required"),{handled:!0,kind:"set_model"};const r=this.sessionRuntime.get(s)??{};return this.sessionRuntime.set(s,{...r,modelId:o}),this.callbacks.sendLocalActionResult(n,"ok",{outcome:"model_set",session_context:{model_id:o,mode_id:r.modeId??null,modelId:o,modeId:r.modeId??null},model_id:o,mode_id:r.modeId??null,available_models:this.availableModels}),{handled:!0,kind:"set_model"}}case"set_mode":{const o=String(i.mode_id??"").trim();if(!o||!s)return this.callbacks.sendLocalActionResult(n,"failed",void 0,"invalid_params","mode_id and session_id are required"),{handled:!0,kind:"set_mode"};const r=this.sessionRuntime.get(s)??{};return this.sessionRuntime.set(s,{...r,modeId:o}),this.callbacks.sendLocalActionResult(n,"ok",{outcome:"mode_set",session_context:{model_id:r.modelId??null,mode_id:o,modelId:r.modelId??null,modeId:o},model_id:r.modelId??null,mode_id:o}),{handled:!0,kind:"set_mode"}}case"get_context":{const o=s?this.sessionRuntime.get(s):void 0;return this.callbacks.sendLocalActionResult(n,"ok",{session_context:{model_id:o?.modelId??null,mode_id:o?.modeId??null,cwd:o?.cwd??null},model_id:o?.modelId??null,mode_id:o?.modeId??null,modelId:o?.modelId??null,modeId:o?.modeId??null,cwd:o?.cwd??null,available_models:this.availableModels}),{handled:!0,kind:"get_context"}}case"get_rate_limits":{const o=this.getRateLimitsSnapshot();return this.callbacks.sendLocalActionResult(n,"ok",o),{handled:!0,kind:"get_rate_limits"}}case"get_session_usage":{const o=s?this.getUsageSnapshot(s):null;return o?this.callbacks.sendLocalActionResult(n,"ok",{adapterType:"cursor",available:!0,sampledAt:o.sampledAt,turns:o.turns,tokenUsage:o.total}):this.callbacks.sendLocalActionResult(n,"ok",{adapterType:"cursor",available:!1,sampledAt:null,turns:0,tokenUsage:null}),{handled:!0,kind:"get_session_usage"}}case"session_control":{const o=String(i.verb??"").trim().toLowerCase();if(o==="restart"&&s)this.deliverStopEvent("__all__",s),this.callbacks.sendLocalActionResult(n,"ok",{outcome:"restarted"});else if(o==="status"){const r=this.sessionRuntime.get(s),d=this.activeBySession.has(s);this.callbacks.sendLocalActionResult(n,"ok",{verb:"status",status:d?"running":"idle",session_context:{model_id:r?.modelId??null,mode_id:r?.modeId??null,cwd:r?.cwd??null},model_id:r?.modelId??null,mode_id:r?.modeId??null,modelId:r?.modelId??null,modeId:r?.modeId??null,cwd:r?.cwd??null})}else this.callbacks.sendLocalActionResult(n,"failed",void 0,"invalid_verb",`Unsupported verb: ${o}`);return{handled:!0,kind:"session_control"}}default:return{handled:!1,kind:"unsupported"}}}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 i of this.activeBySession.values()){const s=i.child?.pid;if(s&&!Y(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(){this.activeBySession.clear()}getMcpConfig(){return null}async probe(e){const t=this.getStatus();return{...await H(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 i=q(t);return{recordPath:t,lastActivityMs:i.mtimeMs,freshMs:Date.now()-i.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(){return{adapterType:"cursor",available:!1,cached:!1,sampledAt:null,rateLimits:null,contextWindow:null,tokenUsage:null}}buildPromptText(e){let t;return!e.contextMessages||e.contextMessages.length===0?t=e.text:t=`${e.contextMessages.map(s=>`[${s.senderId}] ${s.content}`).join(`
3
3
  `)}
4
4
 
5
5
  [Current user message]
6
- ${e.text}`,this.identity.injectOnce(e.adapterSessionId,t)}handleStdoutLineForActive(e,t){if(e.done)return;const i=t.trim();if(!i)return;this.armActiveIdleTimer(e.request.adapterSessionId);const{event:s}=e;let o;try{o=JSON.parse(i)}catch{this.callbacks.sendRawEventEnvelope?.(s.event_id,s.session_id,{type:"raw_text",text:i});return}this.callbacks.sendRawEventEnvelope?.(s.event_id,s.session_id,o);const n=this.extractAssistantText(o);if(n&&(e.seq+=1,this.callbacks.sendStreamChunk(s.event_id,s.session_id,n,e.seq,!1)),o?.type==="result"){const r=String(o?.session_id??"").trim();if(r){const m=this.sessionRuntime.get(e.request.adapterSessionId)??{};this.sessionRuntime.set(e.request.adapterSessionId,{...m,cursorSessionId:r}),this.bindingStore?.setAcpSessionId(e.request.adapterSessionId,r)}const d=o?.usage??{};this.lastUsageBySession.set(e.request.adapterSessionId,{sampledAt:new Date().toISOString(),turns:(this.lastUsageBySession.get(e.request.adapterSessionId)?.turns??0)+1,total:{input:Number(d.inputTokens??0),output:Number(d.outputTokens??0),cacheRead:Number(d.cacheReadTokens??0),cacheWrite:Number(d.cacheWriteTokens??0)}})}}extractAssistantText(e){if(e?.type!=="assistant")return"";const t=e?.message?.content;return Array.isArray(t)?t.map(s=>s?.type==="text"?String(s?.text??""):"").filter(Boolean).join(""):""}armActiveIdleTimer(e){const t=this.activeBySession.get(e);!t||t.done||(t.idleTimer&&clearTimeout(t.idleTimer),t.idleTimer=setTimeout(()=>{t.done||(c.error("cursor-adapter",`Idle timeout (${b/1e3}s) \u2014 killing cursor child: event=${t.event.event_id} session=${e}`),this.killChildWithFallback(t.child),this.finishActive(e,"failed",`idle timeout after ${b/1e3}s`))},b),t.idleTimer.unref?.())}finishActive(e,t,i){const s=this.activeBySession.get(e);s&&(s.done=!0,s.timer&&clearTimeout(s.timer),s.idleTimer&&clearTimeout(s.idleTimer),s.seq>0&&(s.seq+=1,this.callbacks.sendStreamChunk(s.event.event_id,s.event.session_id,"",s.seq,!0)),c.info("cursor-adapter",`job finish: event=${s.event.event_id} session=${s.event.session_id} status=${t}${i?` msg=${i}`:""}`),this.callbacks.sendEventResult(s.event.event_id,t,i),t==="responded"?s.handle.emitDone({status:"completed"}):t==="canceled"?s.handle.emitDone({status:"canceled",error:i}):s.handle.emitDone({status:"failed",error:i}),this.emit("eventDone",s.event.event_id),this.activeBySession.delete(e),this.tryStartNext(e))}shouldRetryWithoutContinue(e){if(!e.usedContinue||e.retryCount>0)return!1;const t=e.stderr.toLowerCase(),i=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 i&&s}ensureWorkspaceMcpAndSkills(e){if(!this.internalApi)return;this.ensureCursorMcpConfig(e,this.internalApi.url);const t=f($(),".cursor","skills"),i=G(t);i.length>0&&c.info("cursor-adapter",`Synced connector skills to ${t}: [${i.join(", ")}]`)}ensureCursorMcpConfig(e,t){try{const i=f(e,".cursor"),s=f(i,"mcp.json");x(i,{recursive:!0});let o={};try{o=JSON.parse(T(s,"utf8"))}catch{o={}}const n={command:process.execPath,args:[N(U(import.meta.url),"../../../mcp/acp-mcp-server.js"),"--api-url",t]};(!o.mcpServers||typeof o.mcpServers!="object")&&(o.mcpServers={}),o.mcpServers.grix=n,C(s,`${JSON.stringify(o,null,2)}
7
- `,"utf8"),this.recordCursorMcpRegistry(e,s,n),c.info("cursor-adapter",`MCP config synced: workspace=${e} file=${s}`)}catch(i){c.warn("cursor-adapter",`Failed to ensure .cursor/mcp.json: ${String(i)}`)}}recordCursorMcpRegistry(e,t,i){try{const s=f(H.data,"cursor"),o=f(s,"mcp-registry.json");x(s,{recursive:!0});let n={};try{n=JSON.parse(T(o,"utf8"))}catch{n={}}n[e]={mcp_path:t,server:i,updated_at:new Date().toISOString()},C(o,`${JSON.stringify(n,null,2)}
8
- `,"utf8")}catch(s){c.warn("cursor-adapter",`Failed to record MCP registry: ${String(s)}`)}}}export{I as CursorAdapter,Q as findCursorTranscript};
6
+ ${e.text}`,this.identity.injectOnce(e.adapterSessionId,t)}handleStdoutLineForActive(e,t){if(e.done)return;const i=t.trim();if(!i)return;const{event:s}=e;let n;try{n=JSON.parse(i)}catch{this.armActiveIdleTimer(e.request.adapterSessionId),this.callbacks.sendRawEventEnvelope?.(s.event_id,s.session_id,{type:"raw_text",text:i});return}if(n?.type==="tool_call"){const r=String(n?.call_id??""),d=String(n?.subtype??"");r&&(d==="started"?e.toolCallsInFlight.add(r):K.has(d)&&e.toolCallsInFlight.delete(r))}this.armActiveIdleTimer(e.request.adapterSessionId),this.callbacks.sendRawEventEnvelope?.(s.event_id,s.session_id,n);const o=this.extractAssistantText(n);if(o&&(e.seq+=1,this.callbacks.sendStreamChunk(s.event_id,s.session_id,o,e.seq,!1)),n?.type==="result"){const r=String(n?.session_id??"").trim();if(r){const m=this.sessionRuntime.get(e.request.adapterSessionId)??{};this.sessionRuntime.set(e.request.adapterSessionId,{...m,cursorSessionId:r}),this.bindingStore?.setAcpSessionId(e.request.adapterSessionId,r)}const d=n?.usage??{};this.lastUsageBySession.set(e.request.adapterSessionId,{sampledAt:new Date().toISOString(),turns:(this.lastUsageBySession.get(e.request.adapterSessionId)?.turns??0)+1,total:{input:Number(d.inputTokens??0),output:Number(d.outputTokens??0),cacheRead:Number(d.cacheReadTokens??0),cacheWrite:Number(d.cacheWriteTokens??0)}})}}extractAssistantText(e){if(e?.type!=="assistant")return"";const t=e?.message?.content;return Array.isArray(t)?t.map(s=>s?.type==="text"?String(s?.text??""):"").filter(Boolean).join(""):""}armActiveIdleTimer(e){const t=this.activeBySession.get(e);if(!t||t.done)return;t.idleTimer&&clearTimeout(t.idleTimer);const i=t.toolCallsInFlight.size>0?X:z;t.idleTimer=setTimeout(()=>{t.done||(c.error("cursor-adapter",`Idle timeout (${i/1e3}s, 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 after ${i/1e3}s`))},i),t.idleTimer.unref?.()}finishActive(e,t,i){const s=this.activeBySession.get(e);s&&(s.done=!0,s.timer&&clearTimeout(s.timer),s.idleTimer&&clearTimeout(s.idleTimer),s.seq>0&&(s.seq+=1,this.callbacks.sendStreamChunk(s.event.event_id,s.event.session_id,"",s.seq,!0)),c.info("cursor-adapter",`job finish: event=${s.event.event_id} session=${s.event.session_id} status=${t}${i?` msg=${i}`:""}`),this.callbacks.sendEventResult(s.event.event_id,t,i),t==="responded"?s.handle.emitDone({status:"completed"}):t==="canceled"?s.handle.emitDone({status:"canceled",error:i}):s.handle.emitDone({status:"failed",error:i}),this.emit("eventDone",s.event.event_id),this.activeBySession.delete(e),this.tryStartNext(e))}shouldRetryWithoutContinue(e){if(!e.usedContinue||e.retryCount>0)return!1;const t=e.stderr.toLowerCase(),i=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 i&&s}ensureWorkspaceMcpAndSkills(e){if(!this.internalApi)return;this.ensureCursorMcpConfig(e,this.internalApi.url);const t=f(x(),".cursor","skills"),i=J(t);i.length>0&&c.info("cursor-adapter",`Synced connector skills to ${t}: [${i.join(", ")}]`)}ensureCursorMcpConfig(e,t){try{const i=f(e,".cursor"),s=f(i,"mcp.json");T(i,{recursive:!0});let n={};try{n=JSON.parse(M(s,"utf8"))}catch{n={}}const o={command:process.execPath,args:[O(j(import.meta.url),"../../../mcp/acp-mcp-server.js"),"--api-url",t]};(!n.mcpServers||typeof n.mcpServers!="object")&&(n.mcpServers={}),n.mcpServers.grix=o,C(s,`${JSON.stringify(n,null,2)}
7
+ `,"utf8"),this.recordCursorMcpRegistry(e,s,o),c.info("cursor-adapter",`MCP config synced: workspace=${e} file=${s}`)}catch(i){c.warn("cursor-adapter",`Failed to ensure .cursor/mcp.json: ${String(i)}`)}}recordCursorMcpRegistry(e,t,i){try{const s=f(U.data,"cursor"),n=f(s,"mcp-registry.json");T(s,{recursive:!0});let o={};try{o=JSON.parse(M(n,"utf8"))}catch{o={}}o[e]={mcp_path:t,server:i,updated_at:new Date().toISOString()},C(n,`${JSON.stringify(o,null,2)}
8
+ `,"utf8")}catch(s){c.warn("cursor-adapter",`Failed to record MCP registry: ${String(s)}`)}}}export{b as CursorAdapter,V as findCursorTranscript};
@@ -1,9 +1,9 @@
1
- import{EventEmitter as I}from"node:events";import{stat as k}from"node:fs/promises";import{existsSync as y,mkdirSync as b,readFileSync as E,writeFileSync as $}from"node:fs";import{join as v,resolve as P,dirname as A}from"node:path";import{homedir as C}from"node:os";import{fileURLToPath as _}from"node:url";import{resolveCommandPath as O,spawnCommand as M,killProcessGroup as T,hasChildProcesses as F}from"../../core/runtime/spawn.js";import{InternalApiServer as B}from"../../core/mcp/internal-api-server.js";import{IdentityInjector as j}from"../shared/identity-injector.js";import{syncDefaultSkillsToDir as D}from"../../default-skills/index.js";import{buildSimpleProbeReport as N}from"../shared/probe-util.js";import{buildOpencodeConfigContent as H}from"./opencode-config.js";import{OpenCodeTransport as L}from"./opencode-transport.js";import{log as n}from"../../core/log/index.js";import{splitTextForAibotProtocol as S}from"../../core/protocol/index.js";class U extends I{adapterSessionId;constructor(e){super(),this.adapterSessionId=e}emitError(e){if(this.listenerCount("error")===0){n.warn("opencode-adapter",`Prompt handle error (no listeners): ${e.message}`);return}this.emit("error",e)}async cancel(){}}const J=200,G=2e3,X=12e4,q=6e5,x=3e4,W="127.0.0.1",z=0,K=1e3;class le extends I{type="opencode";config;callbacks;options;identity;process=null;transport=new L;alive=!1;stopped=!1;internalApi=null;sessions=new Map;activeRun=null;completedEvents=new Set;clientMsgSeq=0;idleTimer=null;pendingPermissions=new Map;permissionHandler=null;constructor(e,s,t){super(),this.config=e,this.callbacks=s,this.options=t??{},this.identity=new j("opencode-adapter",s.getAgentProfile)}onAgentProfileChanged(){this.identity.onProfileChanged()}async start(){await this.startInternalApiAndInjectMcp();const e=this.options.hostname??W,s=this.options.port??z,t=await this.spawnAndWait(e,s),i=this.resolveCwd();await this.transport.connect(t,i),this.transport.on("event",o=>this.handleSseEvent(o)),n.info("opencode-adapter",`Ready (pid=${this.process?.pid}, url=${t})`)}async stop(){if(this.stopped=!0,this.alive=!1,this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.transport.disconnect(),this.internalApi&&(await this.internalApi.stop(),this.internalApi=null),this.process){const e=this.process;try{T(e,"SIGTERM")}catch{}const s=setTimeout(()=>{try{T(e,"SIGKILL")}catch{}},5e3);e.on("exit",()=>clearTimeout(s)),this.process=null}}isAlive(){return this.alive}async createSession(e){const s=e.cwd??this.resolveCwd(),t=await this.transport.createSession({title:`grix-${Date.now()}`});return this.sessions.set(t.id,{ocSessionId:t.id,cwd:s}),n.info("opencode-adapter",`Created OC session ${t.id} for cwd=${s}`),t.id}async resumeSession(e,s){const t=this.sessions.get(e);if(t?.ocSessionId)try{await this.transport.getSession(t.ocSessionId)}catch{n.warn("opencode-adapter",`OC session ${t.ocSessionId} gone, will create new on next prompt`),t.ocSessionId=""}}async destroySession(e){try{await this.transport.deleteSession(e)}catch{}this.sessions.delete(e),this.identity.forgetSession(e)}sendPrompt(e){const s=new U(e.adapterSessionId);return this.sendOcPrompt(e.adapterSessionId,this.buildPromptText(e)).catch(t=>{s.emitError(t instanceof Error?t:new Error(String(t)))}),s}async cancel(e){if(this.activeRun)try{await this.transport.abortSession(this.activeRun.ocSessionId)}catch{}}deliverInboundEvent(e){const{event_id:s,session_id:t,content:i}=e;if(this.completedEvents.has(s)){n.info("opencode-adapter",`Dropping duplicate event ${s}`),this.callbacks.sendEventAck(s,t),this.callbacks.sendEventResult(s,"responded");return}if(!this.alive){n.warn("opencode-adapter",`Dropping event ${s}: process not alive`),this.callbacks.sendEventAck(s,t),this.callbacks.sendEventResult(s,"failed","Agent process not running");return}this.activeRun&&this.activeRun.eventId!==s&&(n.info("opencode-adapter",`steer: ${this.activeRun.eventId} -> ${s}`),this.flushTextBuffer(),this.callbacks.sendEventResult(this.activeRun.eventId,"canceled","steered to new event"),this.clearRun()),n.info("opencode-adapter",`prompt: event=${s} session=${t}`),this.callbacks.sendEventAck(s,t),this.startRun(s,t),this.startComposing(t),this.resetIdleTimer();const o=this.buildPromptTextFromEvent(e);this.sendOcPrompt(t,o).catch(r=>{n.error("opencode-adapter",`prompt_async failed: ${r}`),this.finishRun("failed",String(r))})}deliverStopEvent(e,s){this.activeRun&&this.activeRun.eventId===e&&(n.info("opencode-adapter",`stop: event=${e}`),this.transport.abortSession(this.activeRun.ocSessionId).catch(()=>{}),this.flushTextBuffer(),this.finishRun("canceled","stopped by user"))}setPermissionHandler(e){this.permissionHandler=e}async ping(e){return this.transport.healthCheck()}getStatus(){return{alive:this.alive,busy:this.activeRun!==null,sessions:this.sessions.size}}getActiveEventIds(){return this.activeRun?[this.activeRun.eventId]:[]}clearActiveEventForShutdown(){this.stopIdleTimer(),this.stopTextFlush(),this.activeRun=null}getMcpConfig(){if(!this.internalApi)return null;const e=P(_(import.meta.url),"../../../mcp/acp-mcp-server.js");return{name:"grix-connector-tools",command:process.execPath,args:[e,"--api-url",this.internalApi.url]}}async hasBackgroundWork(){const e=this.process?.pid;return e?F(e,[e]):!1}async probe(e){const s=this.getStatus();return N(this.config.command||"opencode",{alive:s.alive,busy:s.busy,started:!!this.process},e)}async startInternalApiAndInjectMcp(){try{this.internalApi=new B,this.internalApi.setInvokeHandler(async(l,h,f,p)=>this.callbacks.agentInvoke(l,h,p)),await this.internalApi.start(0),n.info("opencode-adapter",`Internal API started at ${this.internalApi.url}`);const e=this.getMcpConfig(),s=process.env.XDG_CONFIG_HOME||v(C(),".config"),t=v(s,"opencode","opencode.json");b(A(t),{recursive:!0});let i={};try{y(t)&&(i=JSON.parse(E(t,"utf8")))}catch{}const o=i.mcp&&typeof i.mcp=="object"?i.mcp:{};o[e.name]={type:"local",command:[e.command,...e.args??[]],enabled:!0},i.mcp=o,$(t,`${JSON.stringify(i,null,2)}
2
- `,"utf8"),n.info("opencode-adapter",`MCP config injected into ${t}`);const r=v(s,"opencode","skills"),c=D(r);c.length>0&&n.info("opencode-adapter",`Synced connector skills to ${r}: [${c.join(", ")}]`)}catch(e){n.warn("opencode-adapter",`Failed to inject MCP tools (non-fatal): ${e instanceof Error?e.message:String(e)}`)}}async handleLocalAction(e){const{action_type:s}=e;return s==="exec_approve"||s==="exec_reject"||s==="permission_approve"||s==="permission_reject"?this.handlePermissionAction(e):{handled:!1,kind:""}}bindSession(e,s){if(n.info("opencode-adapter",`bindSession: ${e} \u2192 ${s}`),!this.sessions.has(e))this.sessions.set(e,{ocSessionId:"",cwd:s});else{const t=this.sessions.get(e);t.cwd=s}}async spawnAndWait(e,s){const t=this.resolveCwd();try{if(!(await k(t)).isDirectory())throw new Error(`Bound path is not a directory: ${t}`)}catch(i){throw String(i?.code??"")==="ENOENT"?new Error(`Bound directory does not exist: ${t}. Please rebind with /grix open <valid-directory>.`):i}return new Promise((i,o)=>{const r=this.config.command||"opencode",l=[...this.config.args??["serve"],`--hostname=${e}`,`--port=${s}`],h={...process.env,...this.config.env},f=H({model:this.options.model,permissionPolicy:this.options.permissionPolicy,provider:this.options.provider});f&&(h.OPENCODE_CONFIG_CONTENT=JSON.stringify(f));const p=O(r,typeof h.PATH=="string"?h.PATH:void 0);n.info("opencode-adapter",`Spawning: ${p} ${l.join(" ")} (cwd=${t})`),this.process=M(p,l,{env:h,cwd:t}).process;let g="",d=!1;const m=setTimeout(()=>{d||(d=!0,o(new Error(`opencode serve did not start after ${x/1e3}s`)))},x);this.process.stdout?.on("data",a=>{if(g+=a.toString(),!d)for(const u of g.split(`
3
- `)){const R=u.match(/opencode server listening on (https?:\/\/[^\s]+)/);if(R){clearTimeout(m),d=!0,this.alive=!0,i(R[1]);return}}}),this.process.stderr?.on("data",a=>{const u=a.toString().trim();u&&n.info("opencode-adapter",`[stderr] ${u}`)}),this.process.on("error",a=>{n.error("opencode-adapter",`Spawn error: ${a.message}`),clearTimeout(m),this.alive=!1,this.transport.disconnect(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed",`Spawn error: ${a.message}`),this.clearRun()),d?this.stopped||this.emit("exit",1):(d=!0,o(a))}),this.process.on("exit",a=>{n.info("opencode-adapter",`Process exited (code=${a})`),clearTimeout(m),this.alive=!1,this.transport.disconnect(),this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed",`Process exited (code=${a})`),this.clearRun()),d?this.stopped||this.emit("exit",a??1):(d=!0,o(new Error(`opencode serve exited with code ${a}`)))})})}async ensureOcSession(e){const s=this.sessions.get(e);if(s?.ocSessionId)try{return await this.transport.getSession(s.ocSessionId),s.ocSessionId}catch{n.warn("opencode-adapter",`OC session ${s.ocSessionId} gone, creating new`),s.ocSessionId=""}const t=s?.cwd??this.resolveCwd(),i=this.options.bindingStore?.getAcpSessionId(e);if(i)try{return await this.transport.getSession(i),this.sessions.set(e,{ocSessionId:i,cwd:t}),n.info("opencode-adapter",`Resumed OC session ${i} for aibot=${e}`),i}catch{n.warn("opencode-adapter",`Persisted OC session ${i} gone, creating new`)}const o=await this.transport.createSession({title:`grix-${e.slice(-8)}`});return this.sessions.set(e,{ocSessionId:o.id,cwd:t}),this.options.bindingStore?.setAcpSessionId(e,o.id),n.info("opencode-adapter",`Created OC session ${o.id} for aibot=${e}`),o.id}async sendOcPrompt(e,s){const t=await this.ensureOcSession(e);this.activeRun&&(this.activeRun.ocSessionId=t),await this.transport.sendPromptAsync(t,{parts:[{type:"text",text:s}]})}handleSseEvent(e){if(!this.stopped)switch(this.resetIdleTimer(),e.type){case"message.part.updated":{if(!this.activeRun)break;const s=e.part,t=e.delta;s.type==="text"?t?this.appendText(t):s.text&&this.appendText(s.text):s.type==="reasoning"?t&&this.callbacks.sendThinking(this.activeRun.eventId,this.activeRun.sessionId,t):s.type==="tool"&&this.handleToolPartUpdate(s,t);break}case"message.updated":{if(!this.activeRun)break;const s=e.info;if(s.role==="assistant"){const t=s;t.error&&n.warn("opencode-adapter",`Message error: ${JSON.stringify(t.error)}`)}break}case"session.idle":{if(!this.activeRun)break;e.sessionID===this.activeRun.ocSessionId&&(this.flushTextBuffer(),this.finishRun("responded"));break}case"session.status":{if(!this.activeRun)break;e.sessionID===this.activeRun.ocSessionId&&e.status.type==="idle"&&(this.flushTextBuffer(),this.finishRun("responded"));break}case"session.error":{if(!this.activeRun)break;const s=e.error,t=s?typeof s=="object"&&"message"in s?s.message:JSON.stringify(s):"unknown session error";n.error("opencode-adapter",`Session error: ${t}`),this.flushTextBuffer(),this.finishRun("failed",t);break}case"permission.updated":{this.handlePermission(e.permission);break}case"session.created":case"session.updated":case"session.deleted":case"session.compacted":case"session.diff":case"file.edited":case"server.connected":break;case"server.instance.disposed":{n.warn("opencode-adapter",`Server instance disposed: ${e.directory}`),this.alive=!1,this.transport.disconnect(),this.activeRun&&(this.flushTextBuffer(),this.callbacks.sendEventResult(this.activeRun.eventId,"failed","server instance disposed"),this.clearRun()),this.stopped||this.emit("exit",-1);break}default:break}}handleToolPartUpdate(e,s){if(!this.activeRun)return;const t=e.state;switch(t.status){case"pending":case"running":{this.flushTextBuffer();const i=JSON.stringify(t.input??{});this.callbacks.sendToolUse(this.activeRun.eventId,this.activeRun.sessionId,e.tool,i);break}case"completed":{const i=t.output??"";this.callbacks.sendToolResult(this.activeRun.eventId,this.activeRun.sessionId,e.tool,i);break}case"error":{const i=t.error??"unknown tool error";this.callbacks.sendToolResult(this.activeRun.eventId,this.activeRun.sessionId,e.tool,`Error: ${i}`);break}}}handlePermission(e){if(!this.activeRun)return;const{eventId:s,sessionId:t,ocSessionId:i}=this.activeRun;if(this.options.permissionPolicy==="fullAuto"){this.transport.respondPermission(i,e.id,"always").catch(r=>{n.warn("opencode-adapter",`Auto-approve failed: ${r}`)});return}this.stopIdleTimer(),this.pendingPermissions.set(s,{permissionId:e.id,ocSessionId:i});const o=JSON.stringify(e.metadata??{});this.callbacks.sendToolUse(s,t,e.type,o)}handlePermissionAction(e){if(!this.activeRun)return{handled:!1,kind:""};const s=this.pendingPermissions.get(this.activeRun.eventId);if(!s)return{handled:!1,kind:""};const{permissionId:t,ocSessionId:i}=s,r=e.action_type==="exec_approve"||e.action_type==="permission_approve"?"once":"reject";return this.pendingPermissions.delete(this.activeRun.eventId),this.transport.respondPermission(i,t,r).then(()=>{n.info("opencode-adapter",`Permission ${r}: ${t}`),this.resetIdleTimer()}).catch(c=>{n.warn("opencode-adapter",`Permission response failed: ${c}`)}),{handled:!0,kind:"permission"}}startRun(e,s){this.activeRun={eventId:e,sessionId:s,ocSessionId:"",chunkSeq:0,clientMsgId:`oc_${++this.clientMsgSeq}_${Date.now()}`,textBuffer:"",flushTimer:null}}finishRun(e,s){const t=this.activeRun;if(!t)return;if(this.completedEvents.add(t.eventId),this.completedEvents.size>K){const c=[...this.completedEvents].slice(-500);this.completedEvents=new Set(c)}this.activeRun=null,this.stopComposing(),this.stopIdleTimer();const i=++t.chunkSeq,o=t.clientMsgId;e==="failed"&&s&&this.callbacks.sendRunError(t.eventId,t.sessionId,s);const r=()=>{this.callbacks.sendFinalStreamChunkReliable?this.callbacks.sendFinalStreamChunkReliable(t.eventId,t.sessionId,i,o).then(()=>{this.callbacks.sendEventResult(t.eventId,e,s)}).catch(()=>{this.callbacks.sendStreamChunk(t.eventId,t.sessionId,"",i,!0,o),this.callbacks.sendEventResult(t.eventId,e,s)}):(this.callbacks.sendStreamChunk(t.eventId,t.sessionId,"",i,!0,o),this.callbacks.sendEventResult(t.eventId,e,s))};if(t.textBuffer){this.stopTextFlush();for(const c of S(t.textBuffer))t.chunkSeq++,this.callbacks.sendStreamChunk(t.eventId,t.sessionId,c,t.chunkSeq,!1,t.clientMsgId);t.textBuffer=""}r(),this.emit("eventDone",t.eventId)}clearRun(){this.activeRun?.flushTimer&&clearTimeout(this.activeRun.flushTimer);const e=this.activeRun?.eventId;this.activeRun=null,e&&this.emit("eventDone",e)}appendText(e){if(this.activeRun){if(this.activeRun.textBuffer+=e,this.activeRun.textBuffer.length>=G){this.flushTextBuffer();return}this.scheduleTextFlush()}}scheduleTextFlush(){!this.activeRun||this.activeRun.flushTimer||(this.activeRun.flushTimer=setTimeout(()=>{this.activeRun&&(this.activeRun.flushTimer=null),this.flushTextBuffer()},J))}flushTextBuffer(){if(this.stopTextFlush(),!(!this.activeRun||!this.activeRun.textBuffer)){for(const e of S(this.activeRun.textBuffer))this.activeRun.chunkSeq++,this.callbacks.sendStreamChunk(this.activeRun.eventId,this.activeRun.sessionId,e,this.activeRun.chunkSeq,!1,this.activeRun.clientMsgId);this.activeRun.textBuffer=""}}stopTextFlush(){this.activeRun?.flushTimer&&(clearTimeout(this.activeRun.flushTimer),this.activeRun.flushTimer=null)}startComposing(e){}stopComposing(){}resetIdleTimer(){if(this.stopIdleTimer(),this.stopped||!this.activeRun)return;const e=this.pendingPermissions.has(this.activeRun.eventId)?q:X;this.idleTimer=setTimeout(()=>{n.error("opencode-adapter","Idle timeout \u2014 emitting exit for respawn"),this.flushTextBuffer(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed","idle timeout"),this.clearRun()),this.emit("exit",-1)},e)}stopIdleTimer(){this.idleTimer&&(clearTimeout(this.idleTimer),this.idleTimer=null)}resolveCwd(){const e=(this.config.options??{}).cwd;return typeof e=="string"&&e?e:process.cwd()}buildPromptText(e){let s=e.text;return e.contextMessages&&e.contextMessages.length>0&&(s=e.contextMessages.map(i=>`[context] ${i.senderId}: ${i.content}`).join(`
1
+ import{EventEmitter as R}from"node:events";import{stat as y}from"node:fs/promises";import{existsSync as k,mkdirSync as b,readFileSync as E,writeFileSync as $}from"node:fs";import{join as v,resolve as C,dirname as P}from"node:path";import{homedir as A}from"node:os";import{fileURLToPath as _}from"node:url";import{resolveCommandPath as O,spawnCommand as F,killProcessGroup as T,hasChildProcesses as M}from"../../core/runtime/spawn.js";import{InternalApiServer as B}from"../../core/mcp/internal-api-server.js";import{IdentityInjector as D}from"../shared/identity-injector.js";import{syncDefaultSkillsToDir as j}from"../../default-skills/index.js";import{buildSimpleProbeReport as N}from"../shared/probe-util.js";import{buildOpencodeConfigContent as H}from"./opencode-config.js";import{OpenCodeTransport as L}from"./opencode-transport.js";import{log as n}from"../../core/log/index.js";import{splitTextForAibotProtocol as S}from"../../core/protocol/index.js";class U extends R{adapterSessionId;constructor(e){super(),this.adapterSessionId=e}emitError(e){if(this.listenerCount("error")===0){n.warn("opencode-adapter",`Prompt handle error (no listeners): ${e.message}`);return}this.emit("error",e)}async cancel(){}}const J=200,G=2e3,z=12e4,X=6e5,x=3e4,q="127.0.0.1",W=0,K=1e3;class he extends R{type="opencode";config;callbacks;options;identity;process=null;transport=new L;alive=!1;stopped=!1;internalApi=null;sessions=new Map;activeRun=null;completedEvents=new Set;clientMsgSeq=0;idleTimer=null;toolCallsInFlight=new Set;pendingPermissions=new Map;permissionHandler=null;constructor(e,s,t){super(),this.config=e,this.callbacks=s,this.options=t??{},this.identity=new D("opencode-adapter",s.getAgentProfile)}onAgentProfileChanged(){this.identity.onProfileChanged()}async start(){await this.startInternalApiAndInjectMcp();const e=this.options.hostname??q,s=this.options.port??W,t=await this.spawnAndWait(e,s),i=this.resolveCwd();await this.transport.connect(t,i),this.transport.on("event",o=>this.handleSseEvent(o)),n.info("opencode-adapter",`Ready (pid=${this.process?.pid}, url=${t})`)}async stop(){if(this.stopped=!0,this.alive=!1,this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.transport.disconnect(),this.internalApi&&(await this.internalApi.stop(),this.internalApi=null),this.process){const e=this.process;try{T(e,"SIGTERM")}catch{}const s=setTimeout(()=>{try{T(e,"SIGKILL")}catch{}},5e3);e.on("exit",()=>clearTimeout(s)),this.process=null}}isAlive(){return this.alive}async createSession(e){const s=e.cwd??this.resolveCwd(),t=await this.transport.createSession({title:`grix-${Date.now()}`});return this.sessions.set(t.id,{ocSessionId:t.id,cwd:s}),n.info("opencode-adapter",`Created OC session ${t.id} for cwd=${s}`),t.id}async resumeSession(e,s){const t=this.sessions.get(e);if(t?.ocSessionId)try{await this.transport.getSession(t.ocSessionId)}catch{n.warn("opencode-adapter",`OC session ${t.ocSessionId} gone, will create new on next prompt`),t.ocSessionId=""}}async destroySession(e){try{await this.transport.deleteSession(e)}catch{}this.sessions.delete(e),this.identity.forgetSession(e)}sendPrompt(e){const s=new U(e.adapterSessionId);return this.sendOcPrompt(e.adapterSessionId,this.buildPromptText(e)).catch(t=>{s.emitError(t instanceof Error?t:new Error(String(t)))}),s}async cancel(e){if(this.activeRun)try{await this.transport.abortSession(this.activeRun.ocSessionId)}catch{}}deliverInboundEvent(e){const{event_id:s,session_id:t,content:i}=e;if(this.completedEvents.has(s)){n.info("opencode-adapter",`Dropping duplicate event ${s}`),this.callbacks.sendEventAck(s,t),this.callbacks.sendEventResult(s,"responded");return}if(!this.alive){n.warn("opencode-adapter",`Dropping event ${s}: process not alive`),this.callbacks.sendEventAck(s,t),this.callbacks.sendEventResult(s,"failed","Agent process not running");return}this.activeRun&&this.activeRun.eventId!==s&&(n.info("opencode-adapter",`steer: ${this.activeRun.eventId} -> ${s}`),this.flushTextBuffer(),this.callbacks.sendEventResult(this.activeRun.eventId,"canceled","steered to new event"),this.clearRun()),n.info("opencode-adapter",`prompt: event=${s} session=${t}`),this.callbacks.sendEventAck(s,t),this.startRun(s,t),this.startComposing(t),this.resetIdleTimer();const o=this.buildPromptTextFromEvent(e);this.sendOcPrompt(t,o).catch(r=>{n.error("opencode-adapter",`prompt_async failed: ${r}`),this.finishRun("failed",String(r))})}deliverStopEvent(e,s){this.activeRun&&this.activeRun.eventId===e&&(n.info("opencode-adapter",`stop: event=${e}`),this.transport.abortSession(this.activeRun.ocSessionId).catch(()=>{}),this.flushTextBuffer(),this.finishRun("canceled","stopped by user"))}setPermissionHandler(e){this.permissionHandler=e}async ping(e){return this.transport.healthCheck()}getStatus(){return{alive:this.alive,busy:this.activeRun!==null,sessions:this.sessions.size}}getActiveEventIds(){return this.activeRun?[this.activeRun.eventId]:[]}clearActiveEventForShutdown(){this.stopIdleTimer(),this.stopTextFlush(),this.activeRun=null}getMcpConfig(){if(!this.internalApi)return null;const e=C(_(import.meta.url),"../../../mcp/acp-mcp-server.js");return{name:"grix-connector-tools",command:process.execPath,args:[e,"--api-url",this.internalApi.url]}}async hasBackgroundWork(){const e=this.process?.pid;return e?M(e,[e]):!1}async probe(e){const s=this.getStatus();return N(this.config.command||"opencode",{alive:s.alive,busy:s.busy,started:!!this.process},e)}async startInternalApiAndInjectMcp(){try{this.internalApi=new B,this.internalApi.setInvokeHandler(async(h,l,f,p)=>this.callbacks.agentInvoke(h,l,p)),await this.internalApi.start(0),n.info("opencode-adapter",`Internal API started at ${this.internalApi.url}`);const e=this.getMcpConfig(),s=process.env.XDG_CONFIG_HOME||v(A(),".config"),t=v(s,"opencode","opencode.json");b(P(t),{recursive:!0});let i={};try{k(t)&&(i=JSON.parse(E(t,"utf8")))}catch{}const o=i.mcp&&typeof i.mcp=="object"?i.mcp:{};o[e.name]={type:"local",command:[e.command,...e.args??[]],enabled:!0},i.mcp=o,$(t,`${JSON.stringify(i,null,2)}
2
+ `,"utf8"),n.info("opencode-adapter",`MCP config injected into ${t}`);const r=v(s,"opencode","skills"),c=j(r);c.length>0&&n.info("opencode-adapter",`Synced connector skills to ${r}: [${c.join(", ")}]`)}catch(e){n.warn("opencode-adapter",`Failed to inject MCP tools (non-fatal): ${e instanceof Error?e.message:String(e)}`)}}async handleLocalAction(e){const{action_type:s}=e;return s==="exec_approve"||s==="exec_reject"||s==="permission_approve"||s==="permission_reject"?this.handlePermissionAction(e):{handled:!1,kind:""}}bindSession(e,s){if(n.info("opencode-adapter",`bindSession: ${e} \u2192 ${s}`),!this.sessions.has(e))this.sessions.set(e,{ocSessionId:"",cwd:s});else{const t=this.sessions.get(e);t.cwd=s}}async spawnAndWait(e,s){const t=this.resolveCwd();try{if(!(await y(t)).isDirectory())throw new Error(`Bound path is not a directory: ${t}`)}catch(i){throw String(i?.code??"")==="ENOENT"?new Error(`Bound directory does not exist: ${t}. Please rebind with /grix open <valid-directory>.`):i}return new Promise((i,o)=>{const r=this.config.command||"opencode",h=[...this.config.args??["serve"],`--hostname=${e}`,`--port=${s}`],l={...process.env,...this.config.env},f=H({model:this.options.model,permissionPolicy:this.options.permissionPolicy,provider:this.options.provider});f&&(l.OPENCODE_CONFIG_CONTENT=JSON.stringify(f));const p=O(r,typeof l.PATH=="string"?l.PATH:void 0);n.info("opencode-adapter",`Spawning: ${p} ${h.join(" ")} (cwd=${t})`),this.process=F(p,h,{env:l,cwd:t}).process;let g="",d=!1;const m=setTimeout(()=>{d||(d=!0,o(new Error(`opencode serve did not start after ${x/1e3}s`)))},x);this.process.stdout?.on("data",a=>{if(g+=a.toString(),!d)for(const u of g.split(`
3
+ `)){const I=u.match(/opencode server listening on (https?:\/\/[^\s]+)/);if(I){clearTimeout(m),d=!0,this.alive=!0,i(I[1]);return}}}),this.process.stderr?.on("data",a=>{const u=a.toString().trim();u&&n.info("opencode-adapter",`[stderr] ${u}`)}),this.process.on("error",a=>{n.error("opencode-adapter",`Spawn error: ${a.message}`),clearTimeout(m),this.alive=!1,this.transport.disconnect(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed",`Spawn error: ${a.message}`),this.clearRun()),d?this.stopped||this.emit("exit",1):(d=!0,o(a))}),this.process.on("exit",a=>{n.info("opencode-adapter",`Process exited (code=${a})`),clearTimeout(m),this.alive=!1,this.transport.disconnect(),this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed",`Process exited (code=${a})`),this.clearRun()),d?this.stopped||this.emit("exit",a??1):(d=!0,o(new Error(`opencode serve exited with code ${a}`)))})})}async ensureOcSession(e){const s=this.sessions.get(e);if(s?.ocSessionId)try{return await this.transport.getSession(s.ocSessionId),s.ocSessionId}catch{n.warn("opencode-adapter",`OC session ${s.ocSessionId} gone, creating new`),s.ocSessionId=""}const t=s?.cwd??this.resolveCwd(),i=this.options.bindingStore?.getAcpSessionId(e);if(i)try{return await this.transport.getSession(i),this.sessions.set(e,{ocSessionId:i,cwd:t}),n.info("opencode-adapter",`Resumed OC session ${i} for aibot=${e}`),i}catch{n.warn("opencode-adapter",`Persisted OC session ${i} gone, creating new`)}const o=await this.transport.createSession({title:`grix-${e.slice(-8)}`});return this.sessions.set(e,{ocSessionId:o.id,cwd:t}),this.options.bindingStore?.setAcpSessionId(e,o.id),n.info("opencode-adapter",`Created OC session ${o.id} for aibot=${e}`),o.id}async sendOcPrompt(e,s){const t=await this.ensureOcSession(e);this.activeRun&&(this.activeRun.ocSessionId=t),await this.transport.sendPromptAsync(t,{parts:[{type:"text",text:s}]})}handleSseEvent(e){if(!this.stopped)switch(this.updateToolInFlight(e),this.resetIdleTimer(),e.type){case"message.part.updated":{if(!this.activeRun)break;const s=e.part,t=e.delta;s.type==="text"?t?this.appendText(t):s.text&&this.appendText(s.text):s.type==="reasoning"?t&&this.callbacks.sendThinking(this.activeRun.eventId,this.activeRun.sessionId,t):s.type==="tool"&&this.handleToolPartUpdate(s,t);break}case"message.updated":{if(!this.activeRun)break;const s=e.info;if(s.role==="assistant"){const t=s;t.error&&n.warn("opencode-adapter",`Message error: ${JSON.stringify(t.error)}`)}break}case"session.idle":{if(!this.activeRun)break;e.sessionID===this.activeRun.ocSessionId&&(this.flushTextBuffer(),this.finishRun("responded"));break}case"session.status":{if(!this.activeRun)break;e.sessionID===this.activeRun.ocSessionId&&e.status.type==="idle"&&(this.flushTextBuffer(),this.finishRun("responded"));break}case"session.error":{if(!this.activeRun)break;const s=e.error,t=s?typeof s=="object"&&"message"in s?s.message:JSON.stringify(s):"unknown session error";n.error("opencode-adapter",`Session error: ${t}`),this.flushTextBuffer(),this.finishRun("failed",t);break}case"permission.updated":{this.handlePermission(e.permission);break}case"session.created":case"session.updated":case"session.deleted":case"session.compacted":case"session.diff":case"file.edited":case"server.connected":break;case"server.instance.disposed":{n.warn("opencode-adapter",`Server instance disposed: ${e.directory}`),this.alive=!1,this.transport.disconnect(),this.activeRun&&(this.flushTextBuffer(),this.callbacks.sendEventResult(this.activeRun.eventId,"failed","server instance disposed"),this.clearRun()),this.stopped||this.emit("exit",-1);break}default:break}}updateToolInFlight(e){if(e.type!=="message.part.updated")return;const s=e.part;if(s?.type!=="tool"||!s.callID)return;const t=s.state?.status;t==="pending"||t==="running"?this.toolCallsInFlight.add(s.callID):(t==="completed"||t==="error")&&this.toolCallsInFlight.delete(s.callID)}handleToolPartUpdate(e,s){if(!this.activeRun)return;const t=e.state;switch(t.status){case"pending":case"running":{this.flushTextBuffer();const i=JSON.stringify(t.input??{});this.callbacks.sendToolUse(this.activeRun.eventId,this.activeRun.sessionId,e.tool,i);break}case"completed":{const i=t.output??"";this.callbacks.sendToolResult(this.activeRun.eventId,this.activeRun.sessionId,e.tool,i);break}case"error":{const i=t.error??"unknown tool error";this.callbacks.sendToolResult(this.activeRun.eventId,this.activeRun.sessionId,e.tool,`Error: ${i}`);break}}}handlePermission(e){if(!this.activeRun)return;const{eventId:s,sessionId:t,ocSessionId:i}=this.activeRun;if(this.options.permissionPolicy==="fullAuto"){this.transport.respondPermission(i,e.id,"always").catch(r=>{n.warn("opencode-adapter",`Auto-approve failed: ${r}`)});return}this.stopIdleTimer(),this.pendingPermissions.set(s,{permissionId:e.id,ocSessionId:i});const o=JSON.stringify(e.metadata??{});this.callbacks.sendToolUse(s,t,e.type,o)}handlePermissionAction(e){if(!this.activeRun)return{handled:!1,kind:""};const s=this.pendingPermissions.get(this.activeRun.eventId);if(!s)return{handled:!1,kind:""};const{permissionId:t,ocSessionId:i}=s,r=e.action_type==="exec_approve"||e.action_type==="permission_approve"?"once":"reject";return this.pendingPermissions.delete(this.activeRun.eventId),this.transport.respondPermission(i,t,r).then(()=>{n.info("opencode-adapter",`Permission ${r}: ${t}`),this.resetIdleTimer()}).catch(c=>{n.warn("opencode-adapter",`Permission response failed: ${c}`)}),{handled:!0,kind:"permission"}}startRun(e,s){this.activeRun={eventId:e,sessionId:s,ocSessionId:"",chunkSeq:0,clientMsgId:`oc_${++this.clientMsgSeq}_${Date.now()}`,textBuffer:"",flushTimer:null}}finishRun(e,s){const t=this.activeRun;if(!t)return;if(this.completedEvents.add(t.eventId),this.completedEvents.size>K){const c=[...this.completedEvents].slice(-500);this.completedEvents=new Set(c)}this.activeRun=null,this.toolCallsInFlight.clear(),this.stopComposing(),this.stopIdleTimer();const i=++t.chunkSeq,o=t.clientMsgId;e==="failed"&&s&&this.callbacks.sendRunError(t.eventId,t.sessionId,s);const r=()=>{this.callbacks.sendFinalStreamChunkReliable?this.callbacks.sendFinalStreamChunkReliable(t.eventId,t.sessionId,o).then(()=>{this.callbacks.sendEventResult(t.eventId,e,s)}).catch(()=>{this.callbacks.sendStreamChunk(t.eventId,t.sessionId,"",i,!0,o),this.callbacks.sendEventResult(t.eventId,e,s)}):(this.callbacks.sendStreamChunk(t.eventId,t.sessionId,"",i,!0,o),this.callbacks.sendEventResult(t.eventId,e,s))};if(t.textBuffer){this.stopTextFlush();for(const c of S(t.textBuffer))t.chunkSeq++,this.callbacks.sendStreamChunk(t.eventId,t.sessionId,c,t.chunkSeq,!1,t.clientMsgId);t.textBuffer=""}r(),this.emit("eventDone",t.eventId)}clearRun(){this.activeRun?.flushTimer&&clearTimeout(this.activeRun.flushTimer);const e=this.activeRun?.eventId;this.activeRun=null,this.toolCallsInFlight.clear(),e&&this.emit("eventDone",e)}appendText(e){if(this.activeRun){if(this.activeRun.textBuffer+=e,this.activeRun.textBuffer.length>=G){this.flushTextBuffer();return}this.scheduleTextFlush()}}scheduleTextFlush(){!this.activeRun||this.activeRun.flushTimer||(this.activeRun.flushTimer=setTimeout(()=>{this.activeRun&&(this.activeRun.flushTimer=null),this.flushTextBuffer()},J))}flushTextBuffer(){if(this.stopTextFlush(),!(!this.activeRun||!this.activeRun.textBuffer)){for(const e of S(this.activeRun.textBuffer))this.activeRun.chunkSeq++,this.callbacks.sendStreamChunk(this.activeRun.eventId,this.activeRun.sessionId,e,this.activeRun.chunkSeq,!1,this.activeRun.clientMsgId);this.activeRun.textBuffer=""}}stopTextFlush(){this.activeRun?.flushTimer&&(clearTimeout(this.activeRun.flushTimer),this.activeRun.flushTimer=null)}startComposing(e){}stopComposing(){}resetIdleTimer(){if(this.stopIdleTimer(),this.stopped||!this.activeRun)return;const e=this.pendingPermissions.has(this.activeRun.eventId)||this.toolCallsInFlight.size>0?X:z;this.idleTimer=setTimeout(()=>{n.error("opencode-adapter",`Idle timeout (${e/1e3}s, toolsInFlight=${this.toolCallsInFlight.size}) \u2014 emitting exit for respawn`),this.flushTextBuffer(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed","idle timeout"),this.clearRun()),this.emit("exit",-1)},e)}stopIdleTimer(){this.idleTimer&&(clearTimeout(this.idleTimer),this.idleTimer=null)}resolveCwd(){const e=(this.config.options??{}).cwd;return typeof e=="string"&&e?e:process.cwd()}buildPromptText(e){let s=e.text;return e.contextMessages&&e.contextMessages.length>0&&(s=e.contextMessages.map(i=>`[context] ${i.senderId}: ${i.content}`).join(`
4
4
  `)+`
5
5
 
6
6
  `+s),this.identity.injectOnce(e.adapterSessionId,s)}buildPromptTextFromEvent(e){let s=e.content||"";if(e.context_messages_json)try{const t=JSON.parse(e.context_messages_json);Array.isArray(t)&&t.length>0&&(s=t.map(o=>`[context] ${o.sender_id??"unknown"}: ${o.content}`).join(`
7
7
  `)+`
8
8
 
9
- `+s)}catch{}return this.identity.injectOnce(e.session_id,s)}}export{le as OpenCodeAdapter};
9
+ `+s)}catch{}return this.identity.injectOnce(e.session_id,s)}}export{he as OpenCodeAdapter};
@@ -1,10 +1,10 @@
1
- import{execFile as I}from"node:child_process";import{promisify as x}from"node:util";import{EventEmitter as u}from"node:events";import{stat as y}from"node:fs/promises";import{mkdirSync as b,writeFileSync as _,unlinkSync as T,statSync as k}from"node:fs";import{join as p,resolve as w}from"node:path";import{fileURLToPath as E}from"node:url";import{tmpdir as P,homedir as f}from"node:os";import{resolveCommandPath as $,spawnCommand as A,killProcessGroup as g,hasChildProcesses as C}from"../../core/runtime/spawn.js";import{InternalApiServer as R}from"../../core/mcp/internal-api-server.js";import{IdentityInjector as M}from"../shared/identity-injector.js";import{syncDefaultSkillsToDir as B}from"../../default-skills/index.js";import{PiTransport as F}from"./pi-transport.js";import{log as a}from"../../core/log/index.js";import{scanSkills as D}from"../claude/skill-scanner.js";import{SessionBindingStore as G}from"../../core/persistence/session-binding-store.js";import{splitTextForAibotProtocol as N}from"../../core/protocol/index.js";import{buildSimpleProbeReport as j}from"../shared/probe-util.js";import{piGrixProviderId as q,removePiGrixProvider as L,writePiGrixProvider as O}from"./pi-provider-config.js";const v=new Map;async function U(l){const t=v.get(l);if(t!==void 0)return t;let e=!1;try{const s=x(I),i=process.platform==="win32",n=i&&/\s/.test(l)&&!l.startsWith('"')?`"${l}"`:l,{stdout:r,stderr:c}=await s(n,["--help"],{timeout:5e3,encoding:"utf-8",...i?{shell:!0}:{}});e=/--mcp-config\b/.test(`${r??""}${c??""}`)}catch{e=!1}return v.set(l,e),a.info("pi-adapter",`Pi --mcp-config \u652F\u6301\u63A2\u6D4B: ${e?"\u652F\u6301":"\u4E0D\u652F\u6301"} (${l})`),e}class H extends u{adapterSessionId;constructor(t){super(),this.adapterSessionId=t}emitDone(t){this.emit("done",t)}emitError(t){if(this.listenerCount("error")===0){a.warn("pi-adapter",`Prompt handle error (no listeners): ${t.message}`);return}this.emit("error",t)}async cancel(){}}const W=12e4,z=500,J=2e3;class S extends u{type="pi";config;callbacks;process=null;transport=new F;alive=!1;stopped=!1;internalApi=null;mcpConfigPath=null;identity;piSessionPath=null;activeEventId=null;activeSessionId=null;isStreaming=!1;streamSeq=0;clientMsgSeq=0;activeClientMsgId=null;thinkingSeq=0;textBuffer="";emittedTextByIndex=new Map;textFlushTimer=null;idleTimer=null;composingSessionId=null;composingInterval=null;doneGuardTimer=null;bindingStore=null;aibotSessionId="";sessionReadyPromise=null;provider;agentName="";constructor(t,e){super(),this.config=t,this.callbacks=e,this.identity=new M("pi-adapter",e.getAgentProfile);const s=t.options??{};this.aibotSessionId=String(s.aibotSessionId??"").trim(),this.agentName=String(s.agentName??"").trim(),this.provider=s.provider,this.bindingStore=s.bindingStore instanceof G?s.bindingStore:null,this.bindingStore&&this.aibotSessionId&&(this.piSessionPath=this.bindingStore.getPiSessionPath(this.aibotSessionId)??null)}async start(){await this.startInternalApi(),await this.spawnPi(),await this.ensureSessionReady(),a.info("pi-adapter",`Ready (pid=${this.process?.pid})`),this.syncModelBinding()}async syncModelBinding(){if(this.aibotSessionId)try{const[t,e]=await Promise.all([this.transport.send("get_available_models"),this.transport.send("get_state")]),s=t.data?.models;if(!s||s.length===0)return;const i=e.data?.model,n={available_models:s.map(r=>({id:r.id,display_name:r.name??r.id}))};i?.id&&(n.model_id=i.id),this.callbacks.sendUpdateBindingCard(this.aibotSessionId,this.isStreaming?"composing":"ready",this.resolveCwd(),n),a.info("pi-adapter",`synced model binding: ${s.length} models, current=${i?.id??"unknown"}`)}catch(t){a.warn("pi-adapter",`syncModelBinding failed (non-fatal): ${t instanceof Error?t.message:String(t)}`)}}async stop(){if(this.stopped=!0,this.alive=!1,this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.cancelDoneGuard(),this.transport.close(),this.internalApi&&(await this.internalApi.stop(),this.internalApi=null),this.mcpConfigPath){try{T(this.mcpConfigPath)}catch{}this.mcpConfigPath=null}if(this.process){const t=this.process;try{g(t,"SIGTERM")}catch{}const e=setTimeout(()=>{try{g(t,"SIGKILL")}catch{}},5e3);t.on("exit",()=>clearTimeout(e)),this.process=null}}isAlive(){return this.alive}async createSession(t){this.sessionReadyPromise=null,await this.createNewSession();const e=this.piSessionPath||`pi-${Date.now()}`;return a.info("pi-adapter",`Session created: ${e} (path=${this.piSessionPath})`),e}async resumeSession(t,e){await this.switchSession()}onAgentProfileChanged(){this.identity.onProfileChanged()}async destroySession(t){this.identity.forgetSession(t),this.piSessionPath=null,this.sessionReadyPromise=null,this.persistPiSessionPath(void 0)}sendPrompt(t){const e=new H(t.adapterSessionId),s=this.buildPromptTextFromRequest(t);return this.ensureSessionReady().then(()=>this.transport.send("prompt",{message:s})).then(i=>{i.success||e.emitDone({status:"failed",error:i.error})}).catch(i=>{e.emitError(i instanceof Error?i:new Error(String(i)))}),e}async cancel(t){try{await this.transport.send("abort")}catch{}}deliverInboundEvent(t){const{event_id:e,session_id:s,content:i}=t,n=this.buildPromptText(t);this.isStreaming?(this.activeEventId&&this.activeEventId!==e&&(a.info("pi-adapter",`steer: cancel ${this.activeEventId} -> ${e}`),this.flushTextBuffer(),this.callbacks.sendEventResult(this.activeEventId,"canceled","steered to new event")),this.activeEventId=e,this.activeSessionId=s,this.resetRunStreamState(),this.startComposing(s,e),this.resetIdleTimer(),this.transport.send("prompt",{message:n,streamingBehavior:"steer"}).catch(r=>{a.error("pi-adapter",`steer failed: ${r}`),this.callbacks.sendEventResult(e,"failed",String(r))})):(a.info("pi-adapter",`prompt: event=${e} session=${s}`),this.activeEventId=e,this.activeSessionId=s,this.isStreaming=!0,this.resetRunStreamState(),this.startComposing(s,e),this.resetIdleTimer(),this.transport.send("prompt",{message:n}).then(r=>{r.success||(a.error("pi-adapter",`prompt rejected: ${r.error}`),this.isStreaming=!1,this.stopComposing(),this.stopIdleTimer(),this.callbacks.sendEventResult(e,"failed",r.error),this.clearActive())}).catch(r=>{a.error("pi-adapter",`prompt error: ${r}`),this.isStreaming=!1,this.stopComposing(),this.stopIdleTimer(),this.callbacks.sendEventResult(e,"failed",String(r)),this.clearActive()}))}deliverStopEvent(t,e){if(this.activeEventId===t){a.info("pi-adapter",`stop: event=${t}, releasing busy immediately`),this.transport.send("abort").catch(()=>{}),this.flushTextBuffer();const s=this.nextStreamSeq(),i=this.activeClientMsgId??void 0;this.callbacks.sendStreamChunk(t,this.activeSessionId??"","",s,!0,i),this.isStreaming=!1,this.stopComposing(),this.stopIdleTimer(),this.callbacks.sendEventResult(t,"canceled","stopped by user"),this.clearActive()}}async handleLocalAction(t){const e=t.action_id;switch(t.action_type){case"set_model":{const s=t.params??{},i=(s.modelId??s.model_id??"").trim();if(!i)return this.callbacks.sendLocalActionResult(e,"failed",void 0,"invalid_model","model_id is required"),{handled:!0,kind:"set_model_error"};try{let n=s.provider?.trim();return n||(n=(await this.transport.send("get_available_models")).data?.models?.find(d=>d.id===i)?.provider?.trim()),n?(await this.transport.send("set_model",{provider:n,modelId:i}),this.callbacks.sendLocalActionResult(e,"ok",{outcome:"model_set",provider:n,modelId:i}),this.syncModelBinding(),{handled:!0,kind:"set_model"}):(this.callbacks.sendLocalActionResult(e,"failed",void 0,"model_not_found",`No provider for model: ${i}`),{handled:!0,kind:"set_model_error"})}catch(n){return this.callbacks.sendLocalActionResult(e,"failed",void 0,"set_model_error",n instanceof Error?n.message:String(n)),{handled:!0,kind:"set_model_error"}}}case"get_context":try{const s=await this.transport.send("get_state");return this.callbacks.sendLocalActionResult(e,"ok",{state:s}),{handled:!0,kind:"get_context"}}catch(s){return this.callbacks.sendLocalActionResult(e,"failed",void 0,"get_context_error",s instanceof Error?s.message:String(s)),{handled:!1,kind:""}}case"pi_extension_ui_response":return this.transport.sendNoWait({type:"extension_ui_response",...t.params??{}}),this.callbacks.sendLocalActionResult(e,"ok"),{handled:!0,kind:"extension_ui_response"};default:return{handled:!1,kind:""}}}setPermissionHandler(t){}async ping(t){try{return await this.transport.send("get_state"),!0}catch{return!1}}getStatus(){return{alive:this.alive,busy:this.isStreaming,sessions:this.piSessionPath?1:0}}getActiveEventIds(){return this.activeEventId?[this.activeEventId]:[]}clearActiveEventForShutdown(){this.stopIdleTimer(),this.activeEventId=null}getMcpConfig(){if(!this.internalApi)return null;const t=w(E(import.meta.url),"../../../mcp/acp-mcp-server.js");return{name:"grix-connector-tools",command:process.execPath,args:[t,"--api-url",this.internalApi.url]}}async hasBackgroundWork(){const t=this.process?.pid;return t?C(t,[t]):!1}async probe(t){const e=this.getStatus();return{...await j(this.config.command||"pi",{alive:e.alive,busy:e.busy,started:!!this.process},t),session:this.probeSessionRecord()}}probeSessionRecord(){const t=this.piSessionPath;if(!t)return{recordPath:null,lastActivityMs:null,freshMs:null};try{const e=k(t);return{recordPath:t,lastActivityMs:e.mtimeMs,freshMs:Date.now()-e.mtimeMs}}catch{return{recordPath:t,lastActivityMs:null,freshMs:null}}}getSupportedCommands(){return[{name:"model",description:"List or set model",args:"[provider:model_id]"},{name:"interrupt",description:"Interrupt current run"},{name:"status",description:"Show session status"},{name:"skills",description:"List available skills"}]}async execCommand(t,e,s){try{if(!this.alive)return{status:"failed",message:"Pi process not running"};switch(t){case"model":{const i=e.trim();if(i){const c=i.indexOf(":");if(c<1)return{status:"failed",message:"Format: provider:model_id"};const d=i.slice(0,c),h=i.slice(c+1);if(!h)return{status:"failed",message:"Format: provider:model_id"};const o=await this.transport.send("set_model",{provider:d,modelId:h});return o?.success?{status:"ok",message:`Model set to ${d}:${h}`}:{status:"failed",message:`Failed to set model: ${o?.error??"unknown error"}`}}const r=(await this.transport.send("get_available_models"))?.data?.models;return r&&r.length>0?{status:"ok",message:`Available models:
1
+ import{execFile as I}from"node:child_process";import{promisify as x}from"node:util";import{EventEmitter as u}from"node:events";import{stat as y}from"node:fs/promises";import{mkdirSync as b,writeFileSync as _,unlinkSync as T,statSync as w}from"node:fs";import{join as p,resolve as k}from"node:path";import{fileURLToPath as E}from"node:url";import{tmpdir as P,homedir as f}from"node:os";import{resolveCommandPath as $,spawnCommand as A,killProcessGroup as g,hasChildProcesses as C}from"../../core/runtime/spawn.js";import{InternalApiServer as R}from"../../core/mcp/internal-api-server.js";import{IdentityInjector as M}from"../shared/identity-injector.js";import{syncDefaultSkillsToDir as F}from"../../default-skills/index.js";import{PiTransport as B}from"./pi-transport.js";import{log as o}from"../../core/log/index.js";import{scanSkills as D}from"../claude/skill-scanner.js";import{SessionBindingStore as G}from"../../core/persistence/session-binding-store.js";import{splitTextForAibotProtocol as N}from"../../core/protocol/index.js";import{buildSimpleProbeReport as j}from"../shared/probe-util.js";import{piGrixProviderId as L,removePiGrixProvider as O,writePiGrixProvider as q}from"./pi-provider-config.js";const v=new Map;async function U(l){const t=v.get(l);if(t!==void 0)return t;let e=!1;try{const s=x(I),i=process.platform==="win32",n=i&&/\s/.test(l)&&!l.startsWith('"')?`"${l}"`:l,{stdout:r,stderr:c}=await s(n,["--help"],{timeout:5e3,encoding:"utf-8",...i?{shell:!0}:{}});e=/--mcp-config\b/.test(`${r??""}${c??""}`)}catch{e=!1}return v.set(l,e),o.info("pi-adapter",`Pi --mcp-config \u652F\u6301\u63A2\u6D4B: ${e?"\u652F\u6301":"\u4E0D\u652F\u6301"} (${l})`),e}class H extends u{adapterSessionId;constructor(t){super(),this.adapterSessionId=t}emitDone(t){this.emit("done",t)}emitError(t){if(this.listenerCount("error")===0){o.warn("pi-adapter",`Prompt handle error (no listeners): ${t.message}`);return}this.emit("error",t)}async cancel(){}}const W=12e4,z=6e5,J=500,K=2e3;class S extends u{type="pi";config;callbacks;process=null;transport=new B;alive=!1;stopped=!1;internalApi=null;mcpConfigPath=null;identity;piSessionPath=null;activeEventId=null;activeSessionId=null;isStreaming=!1;streamSeq=0;clientMsgSeq=0;activeClientMsgId=null;thinkingSeq=0;textBuffer="";emittedTextByIndex=new Map;textFlushTimer=null;idleTimer=null;toolCallsInFlight=new Set;composingSessionId=null;composingInterval=null;doneGuardTimer=null;bindingStore=null;aibotSessionId="";sessionReadyPromise=null;provider;agentName="";constructor(t,e){super(),this.config=t,this.callbacks=e,this.identity=new M("pi-adapter",e.getAgentProfile);const s=t.options??{};this.aibotSessionId=String(s.aibotSessionId??"").trim(),this.agentName=String(s.agentName??"").trim(),this.provider=s.provider,this.bindingStore=s.bindingStore instanceof G?s.bindingStore:null,this.bindingStore&&this.aibotSessionId&&(this.piSessionPath=this.bindingStore.getPiSessionPath(this.aibotSessionId)??null)}async start(){await this.startInternalApi(),await this.spawnPi(),await this.ensureSessionReady(),o.info("pi-adapter",`Ready (pid=${this.process?.pid})`),this.syncModelBinding()}async syncModelBinding(){if(this.aibotSessionId)try{const[t,e]=await Promise.all([this.transport.send("get_available_models"),this.transport.send("get_state")]),s=t.data?.models;if(!s||s.length===0)return;const i=e.data?.model,n={available_models:s.map(r=>({id:r.id,display_name:r.name??r.id}))};i?.id&&(n.model_id=i.id),this.callbacks.sendUpdateBindingCard(this.aibotSessionId,this.isStreaming?"composing":"ready",this.resolveCwd(),n),o.info("pi-adapter",`synced model binding: ${s.length} models, current=${i?.id??"unknown"}`)}catch(t){o.warn("pi-adapter",`syncModelBinding failed (non-fatal): ${t instanceof Error?t.message:String(t)}`)}}async stop(){if(this.stopped=!0,this.alive=!1,this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.cancelDoneGuard(),this.transport.close(),this.internalApi&&(await this.internalApi.stop(),this.internalApi=null),this.mcpConfigPath){try{T(this.mcpConfigPath)}catch{}this.mcpConfigPath=null}if(this.process){const t=this.process;try{g(t,"SIGTERM")}catch{}const e=setTimeout(()=>{try{g(t,"SIGKILL")}catch{}},5e3);t.on("exit",()=>clearTimeout(e)),this.process=null}}isAlive(){return this.alive}async createSession(t){this.sessionReadyPromise=null,await this.createNewSession();const e=this.piSessionPath||`pi-${Date.now()}`;return o.info("pi-adapter",`Session created: ${e} (path=${this.piSessionPath})`),e}async resumeSession(t,e){await this.switchSession()}onAgentProfileChanged(){this.identity.onProfileChanged()}async destroySession(t){this.identity.forgetSession(t),this.piSessionPath=null,this.sessionReadyPromise=null,this.persistPiSessionPath(void 0)}sendPrompt(t){const e=new H(t.adapterSessionId),s=this.buildPromptTextFromRequest(t);return this.ensureSessionReady().then(()=>this.transport.send("prompt",{message:s})).then(i=>{i.success||e.emitDone({status:"failed",error:i.error})}).catch(i=>{e.emitError(i instanceof Error?i:new Error(String(i)))}),e}async cancel(t){try{await this.transport.send("abort")}catch{}}deliverInboundEvent(t){const{event_id:e,session_id:s,content:i}=t,n=this.buildPromptText(t);this.isStreaming?(this.activeEventId&&this.activeEventId!==e&&(o.info("pi-adapter",`steer: cancel ${this.activeEventId} -> ${e}`),this.flushTextBuffer(),this.callbacks.sendEventResult(this.activeEventId,"canceled","steered to new event")),this.activeEventId=e,this.activeSessionId=s,this.resetRunStreamState(),this.startComposing(s,e),this.resetIdleTimer(),this.transport.send("prompt",{message:n,streamingBehavior:"steer"}).catch(r=>{o.error("pi-adapter",`steer failed: ${r}`),this.callbacks.sendEventResult(e,"failed",String(r))})):(o.info("pi-adapter",`prompt: event=${e} session=${s}`),this.activeEventId=e,this.activeSessionId=s,this.isStreaming=!0,this.resetRunStreamState(),this.startComposing(s,e),this.resetIdleTimer(),this.transport.send("prompt",{message:n}).then(r=>{r.success||(o.error("pi-adapter",`prompt rejected: ${r.error}`),this.isStreaming=!1,this.stopComposing(),this.stopIdleTimer(),this.callbacks.sendEventResult(e,"failed",r.error),this.clearActive())}).catch(r=>{o.error("pi-adapter",`prompt error: ${r}`),this.isStreaming=!1,this.stopComposing(),this.stopIdleTimer(),this.callbacks.sendEventResult(e,"failed",String(r)),this.clearActive()}))}deliverStopEvent(t,e){if(this.activeEventId===t){o.info("pi-adapter",`stop: event=${t}, releasing busy immediately`),this.transport.send("abort").catch(()=>{}),this.flushTextBuffer();const s=this.nextStreamSeq(),i=this.activeClientMsgId??void 0;this.callbacks.sendStreamChunk(t,this.activeSessionId??"","",s,!0,i),this.isStreaming=!1,this.stopComposing(),this.stopIdleTimer(),this.callbacks.sendEventResult(t,"canceled","stopped by user"),this.clearActive()}}async handleLocalAction(t){const e=t.action_id;switch(t.action_type){case"set_model":{const s=t.params??{},i=(s.modelId??s.model_id??"").trim();if(!i)return this.callbacks.sendLocalActionResult(e,"failed",void 0,"invalid_model","model_id is required"),{handled:!0,kind:"set_model_error"};try{let n=s.provider?.trim();return n||(n=(await this.transport.send("get_available_models")).data?.models?.find(d=>d.id===i)?.provider?.trim()),n?(await this.transport.send("set_model",{provider:n,modelId:i}),this.callbacks.sendLocalActionResult(e,"ok",{outcome:"model_set",provider:n,modelId:i}),this.syncModelBinding(),{handled:!0,kind:"set_model"}):(this.callbacks.sendLocalActionResult(e,"failed",void 0,"model_not_found",`No provider for model: ${i}`),{handled:!0,kind:"set_model_error"})}catch(n){return this.callbacks.sendLocalActionResult(e,"failed",void 0,"set_model_error",n instanceof Error?n.message:String(n)),{handled:!0,kind:"set_model_error"}}}case"get_context":try{const s=await this.transport.send("get_state");return this.callbacks.sendLocalActionResult(e,"ok",{state:s}),{handled:!0,kind:"get_context"}}catch(s){return this.callbacks.sendLocalActionResult(e,"failed",void 0,"get_context_error",s instanceof Error?s.message:String(s)),{handled:!1,kind:""}}case"pi_extension_ui_response":return this.transport.sendNoWait({type:"extension_ui_response",...t.params??{}}),this.callbacks.sendLocalActionResult(e,"ok"),{handled:!0,kind:"extension_ui_response"};default:return{handled:!1,kind:""}}}setPermissionHandler(t){}async ping(t){try{return await this.transport.send("get_state"),!0}catch{return!1}}getStatus(){return{alive:this.alive,busy:this.isStreaming,sessions:this.piSessionPath?1:0}}getActiveEventIds(){return this.activeEventId?[this.activeEventId]:[]}clearActiveEventForShutdown(){this.stopIdleTimer(),this.activeEventId=null}getMcpConfig(){if(!this.internalApi)return null;const t=k(E(import.meta.url),"../../../mcp/acp-mcp-server.js");return{name:"grix-connector-tools",command:process.execPath,args:[t,"--api-url",this.internalApi.url]}}async hasBackgroundWork(){const t=this.process?.pid;return t?C(t,[t]):!1}async probe(t){const e=this.getStatus();return{...await j(this.config.command||"pi",{alive:e.alive,busy:e.busy,started:!!this.process},t),session:this.probeSessionRecord()}}probeSessionRecord(){const t=this.piSessionPath;if(!t)return{recordPath:null,lastActivityMs:null,freshMs:null};try{const e=w(t);return{recordPath:t,lastActivityMs:e.mtimeMs,freshMs:Date.now()-e.mtimeMs}}catch{return{recordPath:t,lastActivityMs:null,freshMs:null}}}getSupportedCommands(){return[{name:"model",description:"List or set model",args:"[provider:model_id]"},{name:"interrupt",description:"Interrupt current run"},{name:"status",description:"Show session status"},{name:"skills",description:"List available skills"}]}async execCommand(t,e,s){try{if(!this.alive)return{status:"failed",message:"Pi process not running"};switch(t){case"model":{const i=e.trim();if(i){const c=i.indexOf(":");if(c<1)return{status:"failed",message:"Format: provider:model_id"};const d=i.slice(0,c),h=i.slice(c+1);if(!h)return{status:"failed",message:"Format: provider:model_id"};const a=await this.transport.send("set_model",{provider:d,modelId:h});return a?.success?{status:"ok",message:`Model set to ${d}:${h}`}:{status:"failed",message:`Failed to set model: ${a?.error??"unknown error"}`}}const r=(await this.transport.send("get_available_models"))?.data?.models;return r&&r.length>0?{status:"ok",message:`Available models:
2
2
  ${r.map(d=>` ${d.provider??"unknown"}:${d.id} (${d.name??d.id})`).join(`
3
3
  `)}`,data:r}:{status:"ok",message:"No models available",data:[]}}case"interrupt":return this.isStreaming?(await this.transport.send("abort"),this.activeEventId&&this.callbacks.sendEventResult(this.activeEventId,"canceled","interrupted"),this.clearActive(),{status:"ok",message:"Run interrupted"}):{status:"failed",message:"No active run to interrupt"};case"status":{const i=this.getStatus();return{status:"ok",message:`Alive: ${i.alive}, Busy: ${i.busy}, Session: ${this.piSessionPath??"none"}`,data:{alive:i.alive,busy:i.busy,sessions:i.sessions,sessionPath:this.piSessionPath}}}case"skills":{const i=D({mode:"pi"}),n=i.map(r=>`- ${r.name}${r.trigger?` (${r.trigger})`:""} [${r.source}]: ${r.description}`);return{status:"ok",message:n.length>0?n.join(`
4
- `):"No skills found",data:i}}default:return{status:"unsupported",message:`Unknown command: ${t}`}}}catch(i){return{status:"failed",message:i instanceof Error?i.message:String(i)}}}resolveCwd(){if(this.bindingStore&&this.aibotSessionId){const e=this.bindingStore.get(this.aibotSessionId);if(e?.cwd)return e.cwd}const t=(this.config.options??{}).cwd;return typeof t=="string"&&t?t:process.cwd()}async startInternalApi(){try{this.internalApi=new R,this.internalApi.setInvokeHandler(async(n,r,c,d)=>this.callbacks.agentInvoke(n,r,d)),await this.internalApi.start(0),a.info("pi-adapter",`Internal API started at ${this.internalApi.url}`);const t=this.getMcpConfig(),e=p(P(),"grix-pi-mcp");b(e,{recursive:!0}),this.mcpConfigPath=p(e,`mcp-${process.pid}-${Date.now()}.json`),_(this.mcpConfigPath,JSON.stringify({mcpServers:{[t.name]:{command:t.command,args:t.args,directTools:!0,lifecycle:"eager"}}}),"utf8"),a.info("pi-adapter",`MCP config written to ${this.mcpConfigPath}`);const s=p(f(),".pi","agent","skills"),i=B(s);i.length>0&&a.info("pi-adapter",`Synced connector skills to ${s}: [${i.join(", ")}]`)}catch(t){a.warn("pi-adapter",`Failed to start MCP tools (non-fatal): ${t instanceof Error?t.message:String(t)}`)}}async spawnPi(){const t=this.config.command||"pi",e=$(t,typeof process.env.PATH=="string"?process.env.PATH:void 0),s=this.config.args??[],n=s.some(o=>o.startsWith("--mode"))?[...s]:["--mode","rpc",...s],r=String(this.config.env?.PI_CODING_AGENT_DIR??"").trim()||p(f(),".pi","agent"),c=q(this.agentName);this.provider?await O(r,c,this.provider)?(n.includes("--provider")||n.push("--provider",c),!n.includes("--model")&&this.provider.model?.trim()&&n.push("--model",this.provider.model.trim())):a.warn("pi-adapter","provider config incomplete (baseUrl/model required); Grix provider not applied"):await L(r,c).then(o=>{o&&a.info("pi-adapter",`Removed stale Grix provider "${c}" from models.json`)}).catch(o=>{a.warn("pi-adapter",`Failed to clean up Grix provider config: ${o instanceof Error?o.message:String(o)}`)}),this.mcpConfigPath&&!n.some(o=>o==="--mcp-config")&&(await U(e)?n.push("--mcp-config",this.mcpConfigPath):a.warn("pi-adapter","\u5F53\u524D Pi \u4E0D\u652F\u6301 --mcp-config\uFF08\u672A\u68C0\u6D4B\u5230 pi-mcp-adapter \u6269\u5C55\uFF09\uFF0C\u8DF3\u8FC7 MCP \u6CE8\u5165\u4EE5\u907F\u514D\u542F\u52A8\u5931\u8D25"));const d={...process.env,...this.config.env},h=this.resolveCwd();a.info("pi-adapter",`Spawning: ${e} ${n.join(" ")}`);try{if(!(await y(h)).isDirectory())throw new Error(`Bound path is not a directory: ${h}`)}catch(o){throw String(o?.code??"")==="ENOENT"?new Error(`Bound directory does not exist: ${h}. Please rebind with /grix open <valid-directory>.`):o}try{this.process=A(e,n,{env:d,cwd:h}).process}catch(o){throw a.error("pi-adapter",`PI spawn threw: ${o}`),this.alive=!1,o}this.process.on("error",o=>{a.error("pi-adapter",`Spawn error: ${o.message}`),this.alive=!1,this.transport.close(),this.activeEventId&&(this.callbacks.sendEventResult(this.activeEventId,"failed",`Spawn error: ${o.message}`),this.clearActive()),this.stopped||this.emit("exit",1)}),this.process.on("exit",o=>{a.info("pi-adapter",`PI process exited (code=${o})`),this.alive=!1,this.transport.close(),this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.activeEventId&&this.callbacks.sendEventResult(this.activeEventId,"failed",`PI process exited (code=${o})`),this.isStreaming=!1,this.clearActive(),this.stopped||this.emit("exit",o??1)}),this.process.stderr?.on("data",o=>{const m=o.toString().trim();m&&a.info("pi-adapter",`[pi stderr] ${m}`)}),this.transport.on("event",o=>this.handlePiEvent(o)),this.transport.bind(this.process.stdin,this.process.stdout),this.alive=!0}handlePiEvent(t){if(this.stopped)return;switch(this.resetIdleTimer(),t.type){case"message_update":{const s=t.assistantMessageEvent;if(!s)break;const i=s.type;if(i==="text_delta"){const n=s.delta;if(n){const r=typeof s.contentIndex=="number"?s.contentIndex:0;this.rememberEmittedText(r,n),this.appendText(n)}}else if(i==="text_end"){const n=s.content,r=typeof s.contentIndex=="number"?s.contentIndex:0;n&&this.appendMissingText(r,n)}else if(i==="thinking_delta"){const n=s.delta;n&&this.activeEventId&&this.activeSessionId&&(this.thinkingSeq++,this.callbacks.sendThinking(this.activeEventId,this.activeSessionId,n))}else if(i==="done"||i==="error"){if(this.flushTextBuffer(),i==="error"&&this.activeEventId&&this.activeSessionId){const n=s.reason??"stream error";this.callbacks.sendRunError(this.activeEventId,this.activeSessionId,String(n),this.nextStreamSeq(),this.activeClientMsgId??void 0)}this.scheduleDoneGuard()}break}case"tool_execution_start":{if(this.cancelDoneGuard(),this.flushTextBuffer(),!this.activeEventId||!this.activeSessionId)break;const s=t;if(s.toolName){const i=typeof s.args=="object"&&s.args!==null?JSON.stringify(s.args):String(s.args??"");this.callbacks.sendToolUse(this.activeEventId,this.activeSessionId,s.toolName,i)}break}case"tool_execution_end":{if(!this.activeEventId||!this.activeSessionId)break;const s=t,i=K(s.result);i&&this.callbacks.sendToolResult(this.activeEventId,this.activeSessionId,s.toolName,i);break}case"agent_end":{if(a.info("pi-adapter",`agent_end event=${this.activeEventId} sessionId=${this.activeSessionId}`),this.cancelDoneGuard(),this.flushTextBuffer(),this.isStreaming=!1,this.stopComposing(),this.stopIdleTimer(),this.activeEventId){const s=this.activeEventId,i=this.activeSessionId??"",n=this.activeClientMsgId??void 0;this.clearActive(),this.finalizeEvent(s,i,n)}else this.clearActive();break}case"agent_start":{this.cancelDoneGuard(),this.activeEventId&&(a.info("pi-adapter",`agent_start event=${this.activeEventId}`),this.isStreaming=!0);break}default:break}}appendText(t){if(this.textBuffer+=t,this.textBuffer.length>=J){this.flushTextBuffer();return}this.scheduleTextFlush()}scheduleTextFlush(){this.textFlushTimer||(this.textFlushTimer=setTimeout(()=>{this.textFlushTimer=null,this.flushTextBuffer()},z))}flushTextBuffer(){this.stopTextFlush(),!(!this.textBuffer||!this.activeEventId||!this.activeSessionId)&&(this.sendTextChunks(this.textBuffer),this.textBuffer="")}sendTextChunks(t){if(!(!this.activeEventId||!this.activeSessionId))for(const e of N(t))this.callbacks.sendStreamChunk(this.activeEventId,this.activeSessionId,e,this.nextStreamSeq(),!1,this.activeClientMsgId??void 0)}stopTextFlush(){this.textFlushTimer&&(clearTimeout(this.textFlushTimer),this.textFlushTimer=null)}buildPromptText(t){let e=t.content||"";if(t.context_messages_json)try{const s=JSON.parse(t.context_messages_json);Array.isArray(s)&&s.length>0&&(e=s.map(n=>`[context] ${n.sender_id??"unknown"}: ${n.content}`).join(`
4
+ `):"No skills found",data:i}}default:return{status:"unsupported",message:`Unknown command: ${t}`}}}catch(i){return{status:"failed",message:i instanceof Error?i.message:String(i)}}}resolveCwd(){if(this.bindingStore&&this.aibotSessionId){const e=this.bindingStore.get(this.aibotSessionId);if(e?.cwd)return e.cwd}const t=(this.config.options??{}).cwd;return typeof t=="string"&&t?t:process.cwd()}async startInternalApi(){try{this.internalApi=new R,this.internalApi.setInvokeHandler(async(n,r,c,d)=>this.callbacks.agentInvoke(n,r,d)),await this.internalApi.start(0),o.info("pi-adapter",`Internal API started at ${this.internalApi.url}`);const t=this.getMcpConfig(),e=p(P(),"grix-pi-mcp");b(e,{recursive:!0}),this.mcpConfigPath=p(e,`mcp-${process.pid}-${Date.now()}.json`),_(this.mcpConfigPath,JSON.stringify({mcpServers:{[t.name]:{command:t.command,args:t.args,directTools:!0,lifecycle:"eager"}}}),"utf8"),o.info("pi-adapter",`MCP config written to ${this.mcpConfigPath}`);const s=p(f(),".pi","agent","skills"),i=F(s);i.length>0&&o.info("pi-adapter",`Synced connector skills to ${s}: [${i.join(", ")}]`)}catch(t){o.warn("pi-adapter",`Failed to start MCP tools (non-fatal): ${t instanceof Error?t.message:String(t)}`)}}async spawnPi(){const t=this.config.command||"pi",e=$(t,typeof process.env.PATH=="string"?process.env.PATH:void 0),s=this.config.args??[],n=s.some(a=>a.startsWith("--mode"))?[...s]:["--mode","rpc",...s],r=String(this.config.env?.PI_CODING_AGENT_DIR??"").trim()||p(f(),".pi","agent"),c=L(this.agentName);this.provider?await q(r,c,this.provider)?(n.includes("--provider")||n.push("--provider",c),!n.includes("--model")&&this.provider.model?.trim()&&n.push("--model",this.provider.model.trim())):o.warn("pi-adapter","provider config incomplete (baseUrl/model required); Grix provider not applied"):await O(r,c).then(a=>{a&&o.info("pi-adapter",`Removed stale Grix provider "${c}" from models.json`)}).catch(a=>{o.warn("pi-adapter",`Failed to clean up Grix provider config: ${a instanceof Error?a.message:String(a)}`)}),this.mcpConfigPath&&!n.some(a=>a==="--mcp-config")&&(await U(e)?n.push("--mcp-config",this.mcpConfigPath):o.warn("pi-adapter","\u5F53\u524D Pi \u4E0D\u652F\u6301 --mcp-config\uFF08\u672A\u68C0\u6D4B\u5230 pi-mcp-adapter \u6269\u5C55\uFF09\uFF0C\u8DF3\u8FC7 MCP \u6CE8\u5165\u4EE5\u907F\u514D\u542F\u52A8\u5931\u8D25"));const d={...process.env,...this.config.env},h=this.resolveCwd();o.info("pi-adapter",`Spawning: ${e} ${n.join(" ")}`);try{if(!(await y(h)).isDirectory())throw new Error(`Bound path is not a directory: ${h}`)}catch(a){throw String(a?.code??"")==="ENOENT"?new Error(`Bound directory does not exist: ${h}. Please rebind with /grix open <valid-directory>.`):a}try{this.process=A(e,n,{env:d,cwd:h}).process}catch(a){throw o.error("pi-adapter",`PI spawn threw: ${a}`),this.alive=!1,a}this.process.on("error",a=>{o.error("pi-adapter",`Spawn error: ${a.message}`),this.alive=!1,this.transport.close(),this.activeEventId&&(this.callbacks.sendEventResult(this.activeEventId,"failed",`Spawn error: ${a.message}`),this.clearActive()),this.stopped||this.emit("exit",1)}),this.process.on("exit",a=>{o.info("pi-adapter",`PI process exited (code=${a})`),this.alive=!1,this.transport.close(),this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.activeEventId&&this.callbacks.sendEventResult(this.activeEventId,"failed",`PI process exited (code=${a})`),this.isStreaming=!1,this.clearActive(),this.stopped||this.emit("exit",a??1)}),this.process.stderr?.on("data",a=>{const m=a.toString().trim();m&&o.info("pi-adapter",`[pi stderr] ${m}`)}),this.transport.on("event",a=>this.handlePiEvent(a)),this.transport.bind(this.process.stdin,this.process.stdout),this.alive=!0}handlePiEvent(t){if(this.stopped)return;if(t.type==="tool_execution_start"){const s=t;s.toolCallId&&this.toolCallsInFlight.add(s.toolCallId)}else if(t.type==="tool_execution_end"){const s=t;s.toolCallId&&this.toolCallsInFlight.delete(s.toolCallId)}switch(this.resetIdleTimer(),t.type){case"message_update":{const s=t.assistantMessageEvent;if(!s)break;const i=s.type;if(i==="text_delta"){const n=s.delta;if(n){const r=typeof s.contentIndex=="number"?s.contentIndex:0;this.rememberEmittedText(r,n),this.appendText(n)}}else if(i==="text_end"){const n=s.content,r=typeof s.contentIndex=="number"?s.contentIndex:0;n&&this.appendMissingText(r,n)}else if(i==="thinking_delta"){const n=s.delta;n&&this.activeEventId&&this.activeSessionId&&(this.thinkingSeq++,this.callbacks.sendThinking(this.activeEventId,this.activeSessionId,n))}else if(i==="done"||i==="error"){if(this.flushTextBuffer(),i==="error"&&this.activeEventId&&this.activeSessionId){const n=s.reason??"stream error";this.callbacks.sendRunError(this.activeEventId,this.activeSessionId,String(n),this.nextStreamSeq(),this.activeClientMsgId??void 0)}this.scheduleDoneGuard()}break}case"tool_execution_start":{if(this.cancelDoneGuard(),this.flushTextBuffer(),!this.activeEventId||!this.activeSessionId)break;const s=t;if(s.toolName){const i=typeof s.args=="object"&&s.args!==null?JSON.stringify(s.args):String(s.args??"");this.callbacks.sendToolUse(this.activeEventId,this.activeSessionId,s.toolName,i)}break}case"tool_execution_end":{if(!this.activeEventId||!this.activeSessionId)break;const s=t,i=X(s.result);i&&this.callbacks.sendToolResult(this.activeEventId,this.activeSessionId,s.toolName,i);break}case"agent_end":{o.info("pi-adapter",`agent_end event=${this.activeEventId} sessionId=${this.activeSessionId}`),this.cancelDoneGuard(),this.flushTextBuffer(),this.isStreaming=!1,this.stopComposing(),this.stopIdleTimer(),this.completeActiveEvent();break}case"agent_start":{this.cancelDoneGuard(),this.activeEventId&&(o.info("pi-adapter",`agent_start event=${this.activeEventId}`),this.isStreaming=!0);break}default:break}}appendText(t){if(this.textBuffer+=t,this.textBuffer.length>=K){this.flushTextBuffer();return}this.scheduleTextFlush()}scheduleTextFlush(){this.textFlushTimer||(this.textFlushTimer=setTimeout(()=>{this.textFlushTimer=null,this.flushTextBuffer()},J))}flushTextBuffer(){this.stopTextFlush(),!(!this.textBuffer||!this.activeEventId||!this.activeSessionId)&&(this.sendTextChunks(this.textBuffer),this.textBuffer="")}sendTextChunks(t){if(!(!this.activeEventId||!this.activeSessionId))for(const e of N(t))this.callbacks.sendStreamChunk(this.activeEventId,this.activeSessionId,e,this.nextStreamSeq(),!1,this.activeClientMsgId??void 0)}stopTextFlush(){this.textFlushTimer&&(clearTimeout(this.textFlushTimer),this.textFlushTimer=null)}buildPromptText(t){let e=t.content||"";if(t.context_messages_json)try{const s=JSON.parse(t.context_messages_json);Array.isArray(s)&&s.length>0&&(e=s.map(n=>`[context] ${n.sender_id??"unknown"}: ${n.content}`).join(`
5
5
  `)+`
6
6
 
7
7
  `+e)}catch{}return this.identity.injectOnce(t.session_id,e)}buildPromptTextFromRequest(t){let e=t.text;return t.contextMessages&&t.contextMessages.length>0&&(e=t.contextMessages.map(i=>`[context] ${i.senderId}: ${i.content}`).join(`
8
8
  `)+`
9
9
 
10
- `+e),this.identity.injectOnce(t.adapterSessionId,e)}clearActive(){const t=this.activeEventId;this.activeEventId=null,this.activeSessionId=null,this.activeClientMsgId=null,this.textBuffer="",this.emittedTextByIndex.clear(),t&&this.emit("eventDone",t)}resetRunStreamState(){this.streamSeq=0,this.thinkingSeq=0,this.textBuffer="",this.emittedTextByIndex.clear(),this.activeClientMsgId=`pi_${++this.clientMsgSeq}_${Date.now()}`}nextStreamSeq(){return this.streamSeq++,this.streamSeq}sendFinalStreamChunk(t,e){this.callbacks.sendStreamChunk(t,e,"",this.nextStreamSeq(),!0,this.activeClientMsgId??void 0)}finalizeEvent(t,e,s){const i=this.nextStreamSeq();this.callbacks.sendFinalStreamChunkReliable?this.callbacks.sendFinalStreamChunkReliable(t,e,i,s).then(()=>{this.callbacks.sendEventResult(t,"responded"),a.info("pi-adapter",`event completed (reliable) event=${t}`)}).catch(n=>{a.error("pi-adapter",`finalStreamChunk ACK failed event=${t}: ${n}`),this.callbacks.sendStreamChunk(t,e,"",i,!0,s),this.callbacks.sendEventResult(t,"responded"),a.info("pi-adapter",`event completed (fallback) event=${t}`)}):(this.callbacks.sendStreamChunk(t,e,"",i,!0,s),this.callbacks.sendEventResult(t,"responded"),a.info("pi-adapter",`event completed event=${t}`))}rememberEmittedText(t,e){this.emittedTextByIndex.set(t,(this.emittedTextByIndex.get(t)??"")+e)}appendMissingText(t,e){const s=this.emittedTextByIndex.get(t)??"";if(e!==s){if(e.startsWith(s)){const i=e.slice(s.length);i&&(this.rememberEmittedText(t,i),this.appendText(i));return}if(!s){this.rememberEmittedText(t,e),this.appendText(e);return}a.info("pi-adapter",`text_end content mismatch at index=${t}, keeping streamed deltas`)}}startComposing(t,e){this.stopComposing(),this.composingSessionId=t,this.callbacks.sendSessionActivitySet(t,"composing",!0,{ref_event_id:e,ttl_ms:12e4}),this.composingInterval=setInterval(()=>{this.composingSessionId&&this.callbacks.sendSessionActivitySet(this.composingSessionId,"composing",!0,{ttl_ms:12e4})},3e4)}stopComposing(){this.composingInterval&&(clearInterval(this.composingInterval),this.composingInterval=null),this.composingSessionId&&(this.callbacks.sendSessionActivitySet(this.composingSessionId,"composing",!1),this.composingSessionId=null)}static DONE_GUARD_MS=5e3;scheduleDoneGuard(){this.cancelDoneGuard(),!(!this.activeEventId||!this.activeSessionId)&&(this.doneGuardTimer=setTimeout(()=>{if(this.doneGuardTimer=null,!this.activeEventId||!this.isStreaming)return;a.info("pi-adapter",`done guard triggered \u2014 no agent_end received, ending event=${this.activeEventId}`),this.flushTextBuffer(),this.isStreaming=!1,this.stopComposing(),this.stopIdleTimer();const t=this.activeEventId,e=this.activeSessionId??"",s=this.activeClientMsgId??void 0;this.clearActive(),this.finalizeEvent(t,e,s)},S.DONE_GUARD_MS))}cancelDoneGuard(){this.doneGuardTimer&&(clearTimeout(this.doneGuardTimer),this.doneGuardTimer=null)}resetIdleTimer(){this.stopIdleTimer(),!(this.stopped||!this.isStreaming)&&(this.idleTimer=setTimeout(()=>{a.error("pi-adapter","Idle timeout \u2014 emitting exit for respawn"),this.flushTextBuffer(),this.isStreaming=!1,this.stopComposing(),this.activeEventId&&this.callbacks.sendEventResult(this.activeEventId,"failed","idle timeout"),this.clearActive(),this.emit("exit",-1)},W))}stopIdleTimer(){this.idleTimer&&(clearTimeout(this.idleTimer),this.idleTimer=null)}async ensureSessionReady(){return this.sessionReadyPromise?this.sessionReadyPromise:(this.sessionReadyPromise=this.restoreOrCreateSession().finally(()=>{this.sessionReadyPromise=null}),this.sessionReadyPromise)}async restoreOrCreateSession(){try{await this.switchSession();return}catch(t){this.piSessionPath&&a.error("pi-adapter",`switch_session failed, creating new session: ${t}`)}await this.createNewSession()}async switchSession(){if(!this.piSessionPath)throw new Error("no PI session path");const t=await this.transport.send("switch_session",{sessionPath:this.piSessionPath});if(!t.success)throw this.piSessionPath=null,this.persistPiSessionPath(void 0),new Error(`switch_session failed: ${t.error}`)}async createNewSession(){const t=await this.transport.send("new_session");if(!t.success)throw new Error(`new_session failed: ${t.error}`);let e=null;if(e=t.data?.sessionFile??null,!e)try{const i=await this.transport.send("get_state");i.success&&(e=i.data?.sessionFile??null)}catch{}this.piSessionPath=e,this.persistPiSessionPath(this.piSessionPath??void 0)}persistPiSessionPath(t){!this.bindingStore||!this.aibotSessionId||this.bindingStore.setPiSessionPath(this.aibotSessionId,t)}}function K(l){if(!l||typeof l!="object")return"";const t=l.content;if(!Array.isArray(t))return"";const e=[];for(const s of t)if(s&&typeof s=="object"){const i=s;i.type==="text"&&typeof i.text=="string"&&e.push(i.text)}return e.join("")}export{S as PiAdapter};
10
+ `+e),this.identity.injectOnce(t.adapterSessionId,e)}clearActive(){const t=this.activeEventId;this.activeEventId=null,this.activeSessionId=null,this.activeClientMsgId=null,this.textBuffer="",this.emittedTextByIndex.clear(),this.toolCallsInFlight.clear(),t&&this.emit("eventDone",t)}resetRunStreamState(){this.streamSeq=0,this.thinkingSeq=0,this.textBuffer="",this.emittedTextByIndex.clear(),this.activeClientMsgId=`pi_${++this.clientMsgSeq}_${Date.now()}`}nextStreamSeq(){return this.streamSeq++,this.streamSeq}completeActiveEvent(){if(!this.activeEventId){this.clearActive();return}const t=this.activeEventId,e=this.activeSessionId??"",s=this.activeClientMsgId??void 0;this.clearActive(),this.finalizeEvent(t,e,s)}finalizeEvent(t,e,s){const i=this.nextStreamSeq();this.callbacks.sendFinalStreamChunkReliable?this.callbacks.sendFinalStreamChunkReliable(t,e,s).then(()=>{this.callbacks.sendEventResult(t,"responded"),o.info("pi-adapter",`event completed (reliable) event=${t}`)}).catch(n=>{o.error("pi-adapter",`finalStreamChunk ACK failed event=${t}: ${n}`),this.callbacks.sendStreamChunk(t,e,"",i,!0,s),this.callbacks.sendEventResult(t,"responded"),o.info("pi-adapter",`event completed (fallback) event=${t}`)}):(this.callbacks.sendStreamChunk(t,e,"",i,!0,s),this.callbacks.sendEventResult(t,"responded"),o.info("pi-adapter",`event completed event=${t}`))}rememberEmittedText(t,e){this.emittedTextByIndex.set(t,(this.emittedTextByIndex.get(t)??"")+e)}appendMissingText(t,e){const s=this.emittedTextByIndex.get(t)??"";if(e!==s){if(e.startsWith(s)){const i=e.slice(s.length);i&&(this.rememberEmittedText(t,i),this.appendText(i));return}if(!s){this.rememberEmittedText(t,e),this.appendText(e);return}o.info("pi-adapter",`text_end content mismatch at index=${t}, keeping streamed deltas`)}}startComposing(t,e){this.stopComposing(),this.composingSessionId=t,this.callbacks.sendSessionActivitySet(t,"composing",!0,{ref_event_id:e,ttl_ms:12e4}),this.composingInterval=setInterval(()=>{this.composingSessionId&&this.callbacks.sendSessionActivitySet(this.composingSessionId,"composing",!0,{ttl_ms:12e4})},3e4)}stopComposing(){this.composingInterval&&(clearInterval(this.composingInterval),this.composingInterval=null),this.composingSessionId&&(this.callbacks.sendSessionActivitySet(this.composingSessionId,"composing",!1),this.composingSessionId=null)}static DONE_GUARD_MS=5e3;scheduleDoneGuard(){this.cancelDoneGuard(),!(!this.activeEventId||!this.activeSessionId)&&(this.doneGuardTimer=setTimeout(()=>{this.doneGuardTimer=null,!(!this.activeEventId||!this.isStreaming)&&(o.info("pi-adapter",`done guard triggered \u2014 no agent_end received, ending event=${this.activeEventId}`),this.flushTextBuffer(),this.isStreaming=!1,this.stopComposing(),this.stopIdleTimer(),this.completeActiveEvent())},S.DONE_GUARD_MS))}cancelDoneGuard(){this.doneGuardTimer&&(clearTimeout(this.doneGuardTimer),this.doneGuardTimer=null)}resetIdleTimer(){if(this.stopIdleTimer(),this.stopped||!this.isStreaming)return;const t=this.toolCallsInFlight.size>0?z:W;this.idleTimer=setTimeout(()=>{o.error("pi-adapter",`Idle timeout (${t/1e3}s, toolsInFlight=${this.toolCallsInFlight.size}) \u2014 emitting exit for respawn`),this.flushTextBuffer(),this.isStreaming=!1,this.stopComposing(),this.activeEventId&&this.callbacks.sendEventResult(this.activeEventId,"failed","idle timeout"),this.clearActive(),this.emit("exit",-1)},t)}stopIdleTimer(){this.idleTimer&&(clearTimeout(this.idleTimer),this.idleTimer=null)}async ensureSessionReady(){return this.sessionReadyPromise?this.sessionReadyPromise:(this.sessionReadyPromise=this.restoreOrCreateSession().finally(()=>{this.sessionReadyPromise=null}),this.sessionReadyPromise)}async restoreOrCreateSession(){try{await this.switchSession();return}catch(t){this.piSessionPath&&o.error("pi-adapter",`switch_session failed, creating new session: ${t}`)}await this.createNewSession()}async switchSession(){if(!this.piSessionPath)throw new Error("no PI session path");const t=await this.transport.send("switch_session",{sessionPath:this.piSessionPath});if(!t.success)throw this.piSessionPath=null,this.persistPiSessionPath(void 0),new Error(`switch_session failed: ${t.error}`)}async createNewSession(){const t=await this.transport.send("new_session");if(!t.success)throw new Error(`new_session failed: ${t.error}`);let e=null;if(e=t.data?.sessionFile??null,!e)try{const i=await this.transport.send("get_state");i.success&&(e=i.data?.sessionFile??null)}catch{}this.piSessionPath=e,this.persistPiSessionPath(this.piSessionPath??void 0)}persistPiSessionPath(t){!this.bindingStore||!this.aibotSessionId||this.bindingStore.setPiSessionPath(this.aibotSessionId,t)}}function X(l){if(!l||typeof l!="object")return"";const t=l.content;if(!Array.isArray(t))return"";const e=[];for(const s of t)if(s&&typeof s=="object"){const i=s;i.type==="text"&&typeof i.text=="string"&&e.push(i.text)}return e.join("")}export{S as PiAdapter};