grix-connector 3.26.3 → 3.26.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter/agy/agy-adapter.js +5 -5
- package/dist/adapter/codewhale/codewhale-adapter.js +2 -2
- package/dist/adapter/codex/codex-bridge.js +8 -8
- package/dist/adapter/cursor/cursor-adapter.js +7 -7
- package/dist/adapter/opencode/opencode-adapter.js +5 -5
- package/dist/adapter/openhuman/openhuman-adapter.js +4 -4
- package/dist/bridge/bridge.js +7 -7
- package/dist/bridge/send-controller.js +3 -3
- package/dist/core/mcp/index.js +1 -1
- package/dist/core/mcp/server-name.js +1 -0
- package/dist/core/skill-sync/skill-syncer.js +1 -1
- package/dist/mcp/stream-http/security.js +1 -1
- package/package.json +1 -1
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import{createInterface as $}from"node:readline";import{EventEmitter as q}from"node:events";import{randomUUID as
|
|
1
|
+
import{execFileSync as Y}from"node:child_process";import{createInterface as $}from"node:readline";import{EventEmitter as q}from"node:events";import{randomUUID as Z}from"node:crypto";import{mkdirSync as E,readFileSync as L,writeFileSync as P,readdirSync as ee,statSync as D}from"node:fs";import{join as A,resolve as te}from"node:path";import{fileURLToPath as se}from"node:url";import{homedir as N}from"node:os";import{GRIX_PATHS as ne,log as h}from"../../core/log/index.js";import{IdentityInjector as ie}from"../shared/identity-injector.js";import{buildSimpleProbeReport as re}from"../shared/probe-util.js";import{InternalApiServer as oe}from"../../core/mcp/internal-api-server.js";import{resolveMcpServerName as ae,pruneLegacyMcpServerEntries as de}from"../../core/mcp/server-name.js";import{InboundMessageIdInjector as le}from"../shared/inbound-message-id-injector.js";import{syncDefaultSkillsToDir as ce}from"../../default-skills/index.js";import{resolveCommandPath as R,spawnCommand as M,killProcessGroup as I}from"../../core/runtime/spawn.js";import{SessionBindingStore as ue}from"../../core/persistence/session-binding-store.js";import{fetchCursorParameterizedModels as he}from"./fetch-models.js";import{fetchCursorAccountUsage as me}from"./fetch-usage.js";import{buildCursorContextWindow as fe,buildCursorContextWindowFromUsage as pe,resolveCursorContextWindowSize as ge}from"./context-window.js";const Se=12e4,T=300*1e3,F=90*1e3,j=1800*1e3,ye=8192,Q="Summarize this conversation for handoff to a fresh chat. Include: goals, key decisions, important file paths, current status, and next steps. Be concise but complete. Output ONLY the summary text.",O=12e4,W=new Set(["completed","failed","error","errored","canceled","cancelled","rejected"]);function _e(d){const e=d.replace(/ToolCall$/i,"").trim();return e?e.charAt(0).toUpperCase()+e.slice(1):d||"tool"}function U(d){const e=String(d.call_id??d.callId??"").trim(),t=d.tool_call??d.toolCall;if(t&&typeof t=="object"&&!Array.isArray(t)){const n=Object.keys(t);if(n.length>0){const s=n[0],i=t[s],r=i&&typeof i=="object"&&!Array.isArray(i)?i:null;return{toolName:_e(s),toolInput:r?.args??r?.arguments??{},callId:e}}}return{toolName:String(d.name??d.tool_name??d.toolName??"tool").trim()||"tool",toolInput:d.args??d.tool_input??d.toolInput??{},callId:e}}function H(d){return String(d??"").trim().toLowerCase().replace(/[^a-z0-9]/g,"")}function z(d){const e=H(d);return e==="askquestion"||e==="askuserquestion"}function J(d){const e=H(d);return e==="createplan"||e==="exitplanmode"}function ve(d){if(!Array.isArray(d))return;const e=d.map(t=>{if(typeof t=="string")return t.trim();if(!t||typeof t!="object")return"";const n=t,s=String(n.label??"").trim();if(s)return s;const i=String(n.value??"").trim();if(i)return i;const r=String(n.text??"").trim();return r||""}).filter(t=>t.length>0);return e.length>0?e:void 0}function Ae(d){if(!d||typeof d!="object"||Array.isArray(d))return null;const e=d,t=String(e.title??"").trim(),n=e.questions;if(!Array.isArray(n)||n.length===0)return null;const s=[];for(let i=0;i<n.length;i++){const r=n[i];if(!r||typeof r!="object")continue;const o=r,a=t||`Question ${i+1}`,l=String(o.header??o.id??o.question??a).trim()||a,c=String(o.prompt??o.question??"").trim();if(!c)continue;const u=ve(o.options),p=o.multiSelect===!0||o.multi_select===!0;s.push({header:l,prompt:c,...u?{options:u}:{},...p?{multi_select:!0}:{}})}return s.length>0?s:null}function be(d){if(!d||typeof d!="object"||Array.isArray(d))return null;const e=d,t=String(e.name??e.title??"").trim(),n=String(e.overview??"").trim(),s=String(e.plan??"").trim();if(!s&&!n&&!t)return null;const i=[];return t&&i.push(`# ${t}`),n&&i.push(n),s&&i.push(s),i.join(`
|
|
2
2
|
|
|
3
|
-
`)}function
|
|
3
|
+
`)}function ke(d){if(!d||typeof d!="object"||Array.isArray(d))return null;const e=d,t=String(e.type??"").trim().toLowerCase();if(!t)return null;switch(t){case"tool_call":{const n=String(e.subtype??"").trim().toLowerCase(),s=U(e);return z(s.toolName)||J(s.toolName)?null:n==="started"||n==="start"?{type:"tool_call",payload:{tool_name:s.toolName,...s.callId?{tool_call_id:s.callId}:{}}}:(W.has(n),null)}case"error":return{type:"error",payload:{message:String(e.message??e.error??e.result??"cursor error").trim()||"cursor error"}};case"result":{const n=String(e.subtype??"").trim().toLowerCase();return e.is_error===!0||n==="error"?{type:"error",payload:{message:String(e.result??e.message??"cursor run failed").trim()||"cursor run failed"}}:null}default:return null}}function we(d,e){if(!d||typeof d!="object"||Array.isArray(d))return"";const t=d;if(String(t.type??"")!=="assistant")return"";const n=t.model_call_id??t.modelCallId;if(n!=null&&String(n).trim()!=="")return"";const s=t.message,i=s&&typeof s=="object"&&!Array.isArray(s)?s.content:void 0;if(!Array.isArray(i))return"";const r=i.map(o=>{if(!o||typeof o!="object"||Array.isArray(o))return"";const a=o;return a.type==="text"?String(a.text??""):""}).filter(Boolean).join("");if(!r)return"";if(e){if(r===e||e.startsWith(r)&&r.length<e.length)return"";if(r.startsWith(e))return r.slice(e.length)}return r}const G=[{id:"approval",displayName:"\u4EBA\u5DE5\u786E\u8BA4"},{id:"full_auto",displayName:"\u81EA\u7531\u6A21\u5F0F"},{id:"plan",displayName:"\u8BA1\u5212\u6A21\u5F0F"}];function C(d){const e=String(d??"").trim().toLowerCase();return G.some(t=>t.id===e)?e:null}function B(d){try{return process.kill(d,0),!0}catch(e){return e.code==="EPERM"}}function X(d,e,t){if(t<0)return null;let n;try{n=ee(d,{withFileTypes:!0})}catch{return null}for(const s of n){const i=A(d,s.name);if(s.isDirectory()){const r=X(i,e,t-1);if(r)return r}else if(s.isFile()&&s.name.endsWith(e))return i}return null}function K(d,e){if(!d)return null;const t=e||A(N(),".cursor","projects");return X(t,`${d}.jsonl`,6)}function Ie(d){const e=d.context_messages_json;if(e)try{const t=JSON.parse(e);return!Array.isArray(t)||t.length===0?void 0:t.map(n=>({senderId:String(n.sender_id??"unknown"),content:String(n.content??"")}))}catch{return}}function Ce(d,e){const t=[String(d??"")],n=String(e.quoted_message_id??"").trim();n&&t.push(`[quoted_message_id] ${n}`);const s=String(e.attachments_json??"").trim();return s&&t.push(`[attachments_json]
|
|
4
4
|
${s}`),t.filter(i=>i.length>0).join(`
|
|
5
5
|
|
|
6
|
-
`)}class V extends q{adapterSessionId;constructor(e){super(),this.adapterSessionId=e}emitDone(e){this.emit("done",e)}emitError(e){if(this.listenerCount("error")===0){h.warn("cursor-adapter",`Prompt handle error (no listeners): ${e.message}`);return}this.emit("error",e)}async cancel(){}}class x extends q{type="cursor";static STDERR_MAX_CHARS=8192;config;callbacks;alive=!1;stopped=!1;permissionHandler=null;internalApi=null;bindingStore=null;identity;inboundMessageIds=new oe;inboundQueue=[];pendingBySession=new Map;sessions=new Set;sessionRuntime=new Map;sessionSeq=0;lastUsageBySession=new Map;auditCaptures=new Map;completedAuditBoundaries=new Map;lastContextBySession=new Map;pendingHandoffBySession=new Map;pendingQuestionByRequestId=new Map;pendingQuestionAnswerBySession=new Map;cachedAccountRateLimits=null;rateLimitsInflight=null;_availableModels=[];transcriptPathBySession=new Map;activeBySession=new Map;constructor(e,t){super(),this.config=e,this.callbacks=t;const n=e.options??{};this.bindingStore=n.bindingStore instanceof de?n.bindingStore:null,this.identity=new ne("cursor-adapter",t.getAgentProfile)}async start(){this.alive=!0,this.stopped=!1,(this.config.options??{}).mcp_tools!==!1&&(this.internalApi=new re,this.internalApi.setInvokeHandler(async(t,n,s,i)=>this.callbacks.agentInvoke(t,n,i)),await this.internalApi.start()),this.notifyBindingReady(),this.refreshModels().then(()=>this.notifyBindingReady()).catch(()=>{}),this.refreshAccountRateLimits().then(()=>this.notifyBindingReady()).catch(()=>{})}get availableModels(){return this._availableModels}get availableModes(){return G.map(e=>({...e}))}async refreshModels(){try{const e={...process.env,...this.config.env??{}},t=await le({command:this.config.command,env:e,cwd:process.cwd()});t.length>0&&(this._availableModels=t,h.info("cursor-adapter",`Loaded ${t.length} available models`))}catch(e){h.warn("cursor-adapter",`Failed to refresh models: ${e instanceof Error?e.message:String(e)}`)}}async stop(){this.stopped=!0,this.alive=!1;for(const e of this.activeBySession.values())I(e.child,"SIGTERM");this.activeBySession.clear(),this.transcriptPathBySession.clear(),this.internalApi&&(await this.internalApi.stop(),this.internalApi=null)}isAlive(){return this.alive}async createSession(e){const t=`cursor-${Date.now()}-${++this.sessionSeq}`;return this.sessions.add(t),this.sessionRuntime.set(t,{cwd:typeof e.cwd=="string"?e.cwd:void 0,modelId:typeof e.modelId=="string"?e.modelId:void 0,modeId:typeof e.modeId=="string"?e.modeId:void 0}),t}async resumeSession(e,t){this.sessions.add(e);const n=this.sessionRuntime.get(e)??{};this.sessionRuntime.set(e,{...n,cwd:typeof t?.cwd=="string"?t.cwd:n.cwd,modelId:typeof t?.modelId=="string"?t.modelId:n.modelId,modeId:typeof t?.modeId=="string"?t.modeId:n.modeId})}onAgentProfileChanged(){this.identity.onProfileChanged()}async destroySession(e){this.sessions.delete(e),this.sessionRuntime.delete(e),this.transcriptPathBySession.delete(e),this.identity.forgetSession(e),this.inboundMessageIds.forgetSession(e),this.pendingQuestionAnswerBySession.delete(e),this.pendingHandoffBySession.delete(e);for(const[t,n]of this.pendingQuestionByRequestId)n.sessionId===e&&this.pendingQuestionByRequestId.delete(t)}sendPrompt(e){const t=new V(e.adapterSessionId);if(!this.alive||this.stopped)return queueMicrotask(()=>t.emitError(new Error("adapter not running"))),t;const n=this.inboundQueue.shift()??{event_id:`cursor-evt-${Date.now()}`,session_id:e.adapterSessionId,content:e.text},s=e.adapterSessionId,i=this.pendingBySession.get(s)??[];return i.push({event:n,request:e,handle:t}),this.pendingBySession.set(s,i),this.tryStartNext(s),t}tryStartNext(e){if(this.activeBySession.has(e)||!this.alive||this.stopped)return;const t=this.pendingBySession.get(e);if(!t||t.length===0)return;const n=t.shift();t.length===0?this.pendingBySession.delete(e):this.pendingBySession.set(e,t),n&&this.startJob(n,!1,0)}startJob(e,t,n){const{event:s,request:i,handle:r}=e;s.audit?.enabled&&!this.auditCaptures.has(s.event_id)&&this.auditCaptures.set(s.event_id,[]);const o=i.adapterSessionId,a=this.sessionRuntime.get(i.adapterSessionId)??{},l=this.config.options??{},c=[...this.config.args??[]];c.push("-p","--output-format","stream-json"),l.trust!==!1&&c.push("--trust");const u=a.cwd||l.workspace;this.internalApi&&u&&(this.ensureWorkspaceMcpAndSkills(u),c.push("--approve-mcps")),u&&c.push("--workspace",u);const p=a.modelId||l.model;p&&c.push("--model",p);const v=C(a.modeId||l.mode)??"full_auto";v==="full_auto"?c.push("--yolo"):v==="plan"&&c.push("--plan");const y=a.cursorSessionId??this.bindingStore?.getAcpSessionId(i.adapterSessionId),b=!!(y&&l.use_continue!==!1&&!t);b&&c.push("--resume",y),c.push(this.buildPromptText(i));const m={...process.env,...this.config.env??{}},_=R(this.config.command,typeof m.PATH=="string"?m.PATH:void 0);h.info("cursor-adapter",`job start: event=${s.event_id} session=${s.session_id} retry=${n}`);let g;try{g=M(_,c,{cwd:u||process.cwd(),env:m,stdio:["ignore","pipe","pipe"]}).process}catch(S){this.finishActive(o,"failed",`spawn failed: ${S instanceof Error?S.message:String(S)}`);return}this.callbacks.sendEventAck(s.event_id,s.session_id),this.emit("eventStarted",s.event_id,s.session_id);const f={event:s,request:i,handle:r,child:g,seq:0,done:!1,stderr:"",timer:null,idleTimer:null,toolCallsInFlight:new Set,toolCallsStarted:0,assistantTextEmitted:"",streamBuffer:"",lastIoAt:Date.now(),sawResult:!1,retryCount:n,usedContinue:b,workspace:u||process.cwd(),args:c};this.activeBySession.set(o,f),this.notifyBindingReady(o),this.armActiveIdleTimer(o);const k=i.timeoutMs&&i.timeoutMs>0?i.timeoutMs:0;k>0&&(f.timer=setTimeout(()=>{f.done||(I(g,"SIGTERM"),this.finishActive(o,"failed",`cursor agent timeout after ${k}ms`))},k)),$({input:g.stdout}).on("line",S=>this.handleStdoutLineForActive(f,S)),g.stderr?.on("data",S=>{if(f.done)return;const w=f.stderr+String(S??"");f.stderr=w.length>x.STDERR_MAX_CHARS?w.slice(-x.STDERR_MAX_CHARS):w,f.lastIoAt=Date.now(),this.armActiveIdleTimer(o)}),g.once("error",S=>{this.finishActive(o,"failed",`spawn failed: ${String(S?.message??S)}`)}),g.once("close",S=>{if(!f.done)if((S??0)===0)this.finishActive(o,"responded");else{if(this.shouldRetryWithoutContinue(f)){f.timer&&clearTimeout(f.timer),f.idleTimer&&clearTimeout(f.idleTimer),this.activeBySession.delete(o),this.startJob(e,!0,f.retryCount+1);return}const w=f.stderr.trim()||`cursor agent exited with code ${S??-1}`;this.finishActive(o,"failed",w)}})}async cancel(e){const t=this.activeBySession.get(e);t&&(I(t.child,"SIGTERM"),this.finishActive(e,"canceled","canceled"))}deliverInboundEvent(e){const t=String(e.session_id??"").trim();if(!t){h.warn("cursor-adapter",`Dropping event ${e.event_id}: missing session_id`),this.callbacks.sendEventResult(e.event_id,"failed","missing session_id");return}if(!this.alive||this.stopped){h.warn("cursor-adapter",`Dropping event ${e.event_id}: adapter not running`),this.callbacks.sendEventAck(e.event_id,t),this.callbacks.sendEventResult(e.event_id,"failed","adapter not running");return}const n={adapterSessionId:t,text:this.inboundMessageIds.inject(t,ke(String(e.content??""),e),e.msg_id),contextMessages:be(e)},s=new V(t),i=this.pendingBySession.get(t)??[];i.push({event:e,request:n,handle:s}),this.pendingBySession.set(t,i),h.info("cursor-adapter",`inbound queued: event=${e.event_id} session=${t} depth=${i.length}`),this.tryStartNext(t)}cancelSessionWork(e,t){const n=this.pendingBySession.get(e)??[];this.pendingBySession.delete(e);const s=this.activeBySession.get(e);s&&(this.killChildWithFallback(s.child),this.finishActive(e,"canceled",t));for(const i of n)this.callbacks.sendEventAck(i.event.event_id,i.event.session_id),this.callbacks.sendEventResult(i.event.event_id,"canceled",t),i.handle.emitDone({status:"canceled",error:t})}killChildWithFallback(e){I(e,"SIGTERM"),setTimeout(()=>{if(e.exitCode===null&&e.signalCode===null)try{I(e,"SIGKILL")}catch{}},5e3).unref()}deliverStopEvent(e,t){if(t){const n=this.activeBySession.get(t);if(n?.event.event_id===e){this.killChildWithFallback(n.child),this.finishActive(t,"canceled","stopped");return}const s=this.pendingBySession.get(t)??[],i=s.findIndex(r=>r.event.event_id===e);if(i>=0){const[r]=s.splice(i,1);s.length===0?this.pendingBySession.delete(t):this.pendingBySession.set(t,s),this.callbacks.sendEventAck(r.event.event_id,r.event.session_id),this.callbacks.sendEventResult(r.event.event_id,"canceled","stopped"),r.handle.emitDone({status:"canceled",error:"stopped"})}return}for(const[n,s]of this.activeBySession.entries())if(s.event.event_id===e){this.killChildWithFallback(s.child),this.finishActive(n,"canceled","stopped");return}for(const[n,s]of this.pendingBySession.entries()){const i=s.findIndex(o=>o.event.event_id===e);if(i<0)continue;const[r]=s.splice(i,1);s.length===0?this.pendingBySession.delete(n):this.pendingBySession.set(n,s),this.callbacks.sendEventAck(r.event.event_id,r.event.session_id),this.callbacks.sendEventResult(r.event.event_id,"canceled","stopped"),r.handle.emitDone({status:"canceled",error:"stopped"});return}}async handleLocalAction(e){const t=String(e.action_type??"").trim().toLowerCase(),n=e.params??{},s=String(n.session_id??"").trim(),i=e.action_id;if(t==="claude_interaction_reply")return String(n.kind??"").trim()==="permission"?(this.callbacks.sendLocalActionResult(i,"failed",void 0,"unsupported","Cursor adapter does not support interactive permission approvals"),{handled:!0,kind:"permission_unsupported"}):this.handleQuestionReply(e);switch(t){case"set_model":{const r=String(n.model_id??"").trim();if(!r||!s)return this.callbacks.sendLocalActionResult(i,"failed",void 0,"invalid_params","model_id and session_id are required"),{handled:!0,kind:"set_model"};const o=this.sessionRuntime.get(s)??{};this.sessionRuntime.set(s,{...o,modelId:r}),this.notifyBindingReady(s);const a=this.getToolbarMeta(s);return this.callbacks.sendLocalActionResult(i,"ok",{outcome:"model_set",session_context:{model_id:r,mode_id:a.mode_id,modelId:r,modeId:a.mode_id},model_id:r,mode_id:a.mode_id,available_models:this.availableModels,available_modes:this.availableModes}),{handled:!0,kind:"set_model"}}case"set_mode":{const r=C(n.mode_id);if(!r||!s)return this.callbacks.sendLocalActionResult(i,"failed",void 0,"invalid_params","mode_id must be approval, full_auto, or plan and session_id is required"),{handled:!0,kind:"set_mode"};const o=this.sessionRuntime.get(s)??{};this.sessionRuntime.set(s,{...o,modeId:r}),this.notifyBindingReady(s);const a=this.getToolbarMeta(s);return this.callbacks.sendLocalActionResult(i,"ok",{outcome:"mode_set",session_context:{model_id:a.model_id,mode_id:r,modelId:a.model_id,modeId:r},model_id:a.model_id,mode_id:r,available_models:this.availableModels,available_modes:this.availableModes}),{handled:!0,kind:"set_mode"}}case"get_context":{const r=s?this.sessionRuntime.get(s):void 0,o=this.config.options??{},a=this.getToolbarMeta(s);return this.callbacks.sendLocalActionResult(i,"ok",{session_context:{model_id:a.model_id,mode_id:a.mode_id,cwd:r?.cwd??o.workspace??null},model_id:a.model_id,mode_id:a.mode_id,modelId:a.model_id,modeId:a.mode_id,cwd:r?.cwd??o.workspace??null,available_models:this.availableModels,available_modes:this.availableModes}),{handled:!0,kind:"get_context"}}case"get_rate_limits":{await this.refreshAccountRateLimits({force:!0});const r=this.getRateLimitsSnapshot(s||void 0);return this.callbacks.sendLocalActionResult(i,"ok",r),s&&this.notifyBindingReady(s),{handled:!0,kind:"get_rate_limits"}}case"get_session_usage":{const r=s?this.getUsageSnapshot(s):null;if(r){const o={inputTokens:r.total.input,outputTokens:r.total.output,cacheReadInputTokens:r.total.cacheRead,cacheCreationInputTokens:r.total.cacheWrite};this.callbacks.sendLocalActionResult(i,"ok",{sessionId:s,adapterType:"cursor",sampledAt:r.sampledAt,turns:r.turns,total:o,models:[{model:this.getToolbarMeta(s).model_id??"auto",turns:r.turns,total:o}]})}else this.callbacks.sendLocalActionResult(i,"failed",void 0,"usage_not_found","No usage data found for this session");return{handled:!0,kind:"get_session_usage"}}case"session_control":{const r=String(n.verb??"").trim().toLowerCase();if(r==="restart"&&s)this.cancelSessionWork(s,"restarted"),this.callbacks.sendLocalActionResult(i,"ok",{outcome:"restarted"});else if(r==="status"){const o=this.sessionRuntime.get(s),a=this.config.options??{},l=this.getToolbarMeta(s),c=this.activeBySession.has(s);this.callbacks.sendLocalActionResult(i,"ok",{verb:"status",status:c?"running":"idle",session_context:{model_id:l.model_id,mode_id:l.mode_id,cwd:o?.cwd??a.workspace??null},model_id:l.model_id,mode_id:l.mode_id,modelId:l.model_id,modeId:l.mode_id,cwd:o?.cwd??a.workspace??null,available_models:this.availableModels,available_modes:this.availableModes})}else this.callbacks.sendLocalActionResult(i,"failed",void 0,"invalid_verb",`Unsupported verb: ${r}`);return{handled:!0,kind:"session_control"}}default:return{handled:!1,kind:"unsupported"}}}getSupportedCommands(){return[{name:"compact",description:"\u538B\u7F29\u4E0A\u4E0B\u6587",args:"[instructions]"}]}async execCommand(e,t,n){return e!=="compact"?{status:"unsupported",message:`Unsupported command: ${e}`}:this.runCompactHandoff(n,t)}async runCompactHandoff(e,t=""){if(!e)return{status:"failed",message:"session_id is required"};if(this.activeBySession.has(e))return{status:"failed",message:"Cannot compact while a Cursor turn is active"};const n=this.sessionRuntime.get(e)??{},s=this.config.options??{},i=n.cursorSessionId??this.bindingStore?.getAcpSessionId(e)??null;if(!i)return{status:"failed",message:"No Cursor chat to compact (send a message first)"};const r=String(n.cwd||s.workspace||process.cwd()),o=String(n.modelId||s.model||""),a=C(n.modeId||s.mode)??"full_auto",l=t.trim(),c=l?`${Q}
|
|
6
|
+
`)}class V extends q{adapterSessionId;constructor(e){super(),this.adapterSessionId=e}emitDone(e){this.emit("done",e)}emitError(e){if(this.listenerCount("error")===0){h.warn("cursor-adapter",`Prompt handle error (no listeners): ${e.message}`);return}this.emit("error",e)}async cancel(){}}class x extends q{type="cursor";static STDERR_MAX_CHARS=8192;config;callbacks;alive=!1;stopped=!1;permissionHandler=null;internalApi=null;bindingStore=null;identity;inboundMessageIds=new le;inboundQueue=[];pendingBySession=new Map;sessions=new Set;sessionRuntime=new Map;sessionSeq=0;lastUsageBySession=new Map;auditCaptures=new Map;completedAuditBoundaries=new Map;lastContextBySession=new Map;pendingHandoffBySession=new Map;pendingQuestionByRequestId=new Map;pendingQuestionAnswerBySession=new Map;cachedAccountRateLimits=null;rateLimitsInflight=null;_availableModels=[];transcriptPathBySession=new Map;activeBySession=new Map;constructor(e,t){super(),this.config=e,this.callbacks=t;const n=e.options??{};this.bindingStore=n.bindingStore instanceof ue?n.bindingStore:null,this.identity=new ie("cursor-adapter",t.getAgentProfile)}async start(){this.alive=!0,this.stopped=!1,(this.config.options??{}).mcp_tools!==!1&&(this.internalApi=new oe,this.internalApi.setInvokeHandler(async(t,n,s,i)=>this.callbacks.agentInvoke(t,n,i)),await this.internalApi.start()),this.notifyBindingReady(),this.refreshModels().then(()=>this.notifyBindingReady()).catch(()=>{}),this.refreshAccountRateLimits().then(()=>this.notifyBindingReady()).catch(()=>{})}get availableModels(){return this._availableModels}get availableModes(){return G.map(e=>({...e}))}async refreshModels(){try{const e={...process.env,...this.config.env??{}},t=await he({command:this.config.command,env:e,cwd:process.cwd()});t.length>0&&(this._availableModels=t,h.info("cursor-adapter",`Loaded ${t.length} available models`))}catch(e){h.warn("cursor-adapter",`Failed to refresh models: ${e instanceof Error?e.message:String(e)}`)}}async stop(){this.stopped=!0,this.alive=!1;for(const e of this.activeBySession.values())I(e.child,"SIGTERM");this.activeBySession.clear(),this.transcriptPathBySession.clear(),this.internalApi&&(await this.internalApi.stop(),this.internalApi=null)}isAlive(){return this.alive}async createSession(e){const t=`cursor-${Date.now()}-${++this.sessionSeq}`;return this.sessions.add(t),this.sessionRuntime.set(t,{cwd:typeof e.cwd=="string"?e.cwd:void 0,modelId:typeof e.modelId=="string"?e.modelId:void 0,modeId:typeof e.modeId=="string"?e.modeId:void 0}),t}async resumeSession(e,t){this.sessions.add(e);const n=this.sessionRuntime.get(e)??{};this.sessionRuntime.set(e,{...n,cwd:typeof t?.cwd=="string"?t.cwd:n.cwd,modelId:typeof t?.modelId=="string"?t.modelId:n.modelId,modeId:typeof t?.modeId=="string"?t.modeId:n.modeId})}onAgentProfileChanged(){this.identity.onProfileChanged()}async destroySession(e){this.sessions.delete(e),this.sessionRuntime.delete(e),this.transcriptPathBySession.delete(e),this.identity.forgetSession(e),this.inboundMessageIds.forgetSession(e),this.pendingQuestionAnswerBySession.delete(e),this.pendingHandoffBySession.delete(e);for(const[t,n]of this.pendingQuestionByRequestId)n.sessionId===e&&this.pendingQuestionByRequestId.delete(t)}sendPrompt(e){const t=new V(e.adapterSessionId);if(!this.alive||this.stopped)return queueMicrotask(()=>t.emitError(new Error("adapter not running"))),t;const n=this.inboundQueue.shift()??{event_id:`cursor-evt-${Date.now()}`,session_id:e.adapterSessionId,content:e.text},s=e.adapterSessionId,i=this.pendingBySession.get(s)??[];return i.push({event:n,request:e,handle:t}),this.pendingBySession.set(s,i),this.tryStartNext(s),t}tryStartNext(e){if(this.activeBySession.has(e)||!this.alive||this.stopped)return;const t=this.pendingBySession.get(e);if(!t||t.length===0)return;const n=t.shift();t.length===0?this.pendingBySession.delete(e):this.pendingBySession.set(e,t),n&&this.startJob(n,!1,0)}startJob(e,t,n){const{event:s,request:i,handle:r}=e;s.audit?.enabled&&!this.auditCaptures.has(s.event_id)&&this.auditCaptures.set(s.event_id,[]);const o=i.adapterSessionId,a=this.sessionRuntime.get(i.adapterSessionId)??{},l=this.config.options??{},c=[...this.config.args??[]];c.push("-p","--output-format","stream-json"),l.trust!==!1&&c.push("--trust");const u=a.cwd||l.workspace;this.internalApi&&u&&(this.ensureWorkspaceMcpAndSkills(u),c.push("--approve-mcps")),u&&c.push("--workspace",u);const p=a.modelId||l.model;p&&c.push("--model",p);const v=C(a.modeId||l.mode)??"full_auto";v==="full_auto"?c.push("--yolo"):v==="plan"&&c.push("--plan");const y=a.cursorSessionId??this.bindingStore?.getAcpSessionId(i.adapterSessionId),b=!!(y&&l.use_continue!==!1&&!t);b&&c.push("--resume",y),c.push(this.buildPromptText(i));const m={...process.env,...this.config.env??{}},_=R(this.config.command,typeof m.PATH=="string"?m.PATH:void 0);h.info("cursor-adapter",`job start: event=${s.event_id} session=${s.session_id} retry=${n}`);let g;try{g=M(_,c,{cwd:u||process.cwd(),env:m,stdio:["ignore","pipe","pipe"]}).process}catch(S){this.finishActive(o,"failed",`spawn failed: ${S instanceof Error?S.message:String(S)}`);return}this.callbacks.sendEventAck(s.event_id,s.session_id),this.emit("eventStarted",s.event_id,s.session_id);const f={event:s,request:i,handle:r,child:g,seq:0,done:!1,stderr:"",timer:null,idleTimer:null,toolCallsInFlight:new Set,toolCallsStarted:0,assistantTextEmitted:"",streamBuffer:"",lastIoAt:Date.now(),sawResult:!1,retryCount:n,usedContinue:b,workspace:u||process.cwd(),args:c};this.activeBySession.set(o,f),this.notifyBindingReady(o),this.armActiveIdleTimer(o);const k=i.timeoutMs&&i.timeoutMs>0?i.timeoutMs:0;k>0&&(f.timer=setTimeout(()=>{f.done||(I(g,"SIGTERM"),this.finishActive(o,"failed",`cursor agent timeout after ${k}ms`))},k)),$({input:g.stdout}).on("line",S=>this.handleStdoutLineForActive(f,S)),g.stderr?.on("data",S=>{if(f.done)return;const w=f.stderr+String(S??"");f.stderr=w.length>x.STDERR_MAX_CHARS?w.slice(-x.STDERR_MAX_CHARS):w,f.lastIoAt=Date.now(),this.armActiveIdleTimer(o)}),g.once("error",S=>{this.finishActive(o,"failed",`spawn failed: ${String(S?.message??S)}`)}),g.once("close",S=>{if(!f.done)if((S??0)===0)this.finishActive(o,"responded");else{if(this.shouldRetryWithoutContinue(f)){f.timer&&clearTimeout(f.timer),f.idleTimer&&clearTimeout(f.idleTimer),this.activeBySession.delete(o),this.startJob(e,!0,f.retryCount+1);return}const w=f.stderr.trim()||`cursor agent exited with code ${S??-1}`;this.finishActive(o,"failed",w)}})}async cancel(e){const t=this.activeBySession.get(e);t&&(I(t.child,"SIGTERM"),this.finishActive(e,"canceled","canceled"))}deliverInboundEvent(e){const t=String(e.session_id??"").trim();if(!t){h.warn("cursor-adapter",`Dropping event ${e.event_id}: missing session_id`),this.callbacks.sendEventResult(e.event_id,"failed","missing session_id");return}if(!this.alive||this.stopped){h.warn("cursor-adapter",`Dropping event ${e.event_id}: adapter not running`),this.callbacks.sendEventAck(e.event_id,t),this.callbacks.sendEventResult(e.event_id,"failed","adapter not running");return}const n={adapterSessionId:t,text:this.inboundMessageIds.inject(t,Ce(String(e.content??""),e),e.msg_id),contextMessages:Ie(e)},s=new V(t),i=this.pendingBySession.get(t)??[];i.push({event:e,request:n,handle:s}),this.pendingBySession.set(t,i),h.info("cursor-adapter",`inbound queued: event=${e.event_id} session=${t} depth=${i.length}`),this.tryStartNext(t)}cancelSessionWork(e,t){const n=this.pendingBySession.get(e)??[];this.pendingBySession.delete(e);const s=this.activeBySession.get(e);s&&(this.killChildWithFallback(s.child),this.finishActive(e,"canceled",t));for(const i of n)this.callbacks.sendEventAck(i.event.event_id,i.event.session_id),this.callbacks.sendEventResult(i.event.event_id,"canceled",t),i.handle.emitDone({status:"canceled",error:t})}killChildWithFallback(e){I(e,"SIGTERM"),setTimeout(()=>{if(e.exitCode===null&&e.signalCode===null)try{I(e,"SIGKILL")}catch{}},5e3).unref()}deliverStopEvent(e,t){if(t){const n=this.activeBySession.get(t);if(n?.event.event_id===e){this.killChildWithFallback(n.child),this.finishActive(t,"canceled","stopped");return}const s=this.pendingBySession.get(t)??[],i=s.findIndex(r=>r.event.event_id===e);if(i>=0){const[r]=s.splice(i,1);s.length===0?this.pendingBySession.delete(t):this.pendingBySession.set(t,s),this.callbacks.sendEventAck(r.event.event_id,r.event.session_id),this.callbacks.sendEventResult(r.event.event_id,"canceled","stopped"),r.handle.emitDone({status:"canceled",error:"stopped"})}return}for(const[n,s]of this.activeBySession.entries())if(s.event.event_id===e){this.killChildWithFallback(s.child),this.finishActive(n,"canceled","stopped");return}for(const[n,s]of this.pendingBySession.entries()){const i=s.findIndex(o=>o.event.event_id===e);if(i<0)continue;const[r]=s.splice(i,1);s.length===0?this.pendingBySession.delete(n):this.pendingBySession.set(n,s),this.callbacks.sendEventAck(r.event.event_id,r.event.session_id),this.callbacks.sendEventResult(r.event.event_id,"canceled","stopped"),r.handle.emitDone({status:"canceled",error:"stopped"});return}}async handleLocalAction(e){const t=String(e.action_type??"").trim().toLowerCase(),n=e.params??{},s=String(n.session_id??"").trim(),i=e.action_id;if(t==="claude_interaction_reply")return String(n.kind??"").trim()==="permission"?(this.callbacks.sendLocalActionResult(i,"failed",void 0,"unsupported","Cursor adapter does not support interactive permission approvals"),{handled:!0,kind:"permission_unsupported"}):this.handleQuestionReply(e);switch(t){case"set_model":{const r=String(n.model_id??"").trim();if(!r||!s)return this.callbacks.sendLocalActionResult(i,"failed",void 0,"invalid_params","model_id and session_id are required"),{handled:!0,kind:"set_model"};const o=this.sessionRuntime.get(s)??{};this.sessionRuntime.set(s,{...o,modelId:r}),this.notifyBindingReady(s);const a=this.getToolbarMeta(s);return this.callbacks.sendLocalActionResult(i,"ok",{outcome:"model_set",session_context:{model_id:r,mode_id:a.mode_id,modelId:r,modeId:a.mode_id},model_id:r,mode_id:a.mode_id,available_models:this.availableModels,available_modes:this.availableModes}),{handled:!0,kind:"set_model"}}case"set_mode":{const r=C(n.mode_id);if(!r||!s)return this.callbacks.sendLocalActionResult(i,"failed",void 0,"invalid_params","mode_id must be approval, full_auto, or plan and session_id is required"),{handled:!0,kind:"set_mode"};const o=this.sessionRuntime.get(s)??{};this.sessionRuntime.set(s,{...o,modeId:r}),this.notifyBindingReady(s);const a=this.getToolbarMeta(s);return this.callbacks.sendLocalActionResult(i,"ok",{outcome:"mode_set",session_context:{model_id:a.model_id,mode_id:r,modelId:a.model_id,modeId:r},model_id:a.model_id,mode_id:r,available_models:this.availableModels,available_modes:this.availableModes}),{handled:!0,kind:"set_mode"}}case"get_context":{const r=s?this.sessionRuntime.get(s):void 0,o=this.config.options??{},a=this.getToolbarMeta(s);return this.callbacks.sendLocalActionResult(i,"ok",{session_context:{model_id:a.model_id,mode_id:a.mode_id,cwd:r?.cwd??o.workspace??null},model_id:a.model_id,mode_id:a.mode_id,modelId:a.model_id,modeId:a.mode_id,cwd:r?.cwd??o.workspace??null,available_models:this.availableModels,available_modes:this.availableModes}),{handled:!0,kind:"get_context"}}case"get_rate_limits":{await this.refreshAccountRateLimits({force:!0});const r=this.getRateLimitsSnapshot(s||void 0);return this.callbacks.sendLocalActionResult(i,"ok",r),s&&this.notifyBindingReady(s),{handled:!0,kind:"get_rate_limits"}}case"get_session_usage":{const r=s?this.getUsageSnapshot(s):null;if(r){const o={inputTokens:r.total.input,outputTokens:r.total.output,cacheReadInputTokens:r.total.cacheRead,cacheCreationInputTokens:r.total.cacheWrite};this.callbacks.sendLocalActionResult(i,"ok",{sessionId:s,adapterType:"cursor",sampledAt:r.sampledAt,turns:r.turns,total:o,models:[{model:this.getToolbarMeta(s).model_id??"auto",turns:r.turns,total:o}]})}else this.callbacks.sendLocalActionResult(i,"failed",void 0,"usage_not_found","No usage data found for this session");return{handled:!0,kind:"get_session_usage"}}case"session_control":{const r=String(n.verb??"").trim().toLowerCase();if(r==="restart"&&s)this.cancelSessionWork(s,"restarted"),this.callbacks.sendLocalActionResult(i,"ok",{outcome:"restarted"});else if(r==="status"){const o=this.sessionRuntime.get(s),a=this.config.options??{},l=this.getToolbarMeta(s),c=this.activeBySession.has(s);this.callbacks.sendLocalActionResult(i,"ok",{verb:"status",status:c?"running":"idle",session_context:{model_id:l.model_id,mode_id:l.mode_id,cwd:o?.cwd??a.workspace??null},model_id:l.model_id,mode_id:l.mode_id,modelId:l.model_id,modeId:l.mode_id,cwd:o?.cwd??a.workspace??null,available_models:this.availableModels,available_modes:this.availableModes})}else this.callbacks.sendLocalActionResult(i,"failed",void 0,"invalid_verb",`Unsupported verb: ${r}`);return{handled:!0,kind:"session_control"}}default:return{handled:!1,kind:"unsupported"}}}getSupportedCommands(){return[{name:"compact",description:"\u538B\u7F29\u4E0A\u4E0B\u6587",args:"[instructions]"}]}async execCommand(e,t,n){return e!=="compact"?{status:"unsupported",message:`Unsupported command: ${e}`}:this.runCompactHandoff(n,t)}async runCompactHandoff(e,t=""){if(!e)return{status:"failed",message:"session_id is required"};if(this.activeBySession.has(e))return{status:"failed",message:"Cannot compact while a Cursor turn is active"};const n=this.sessionRuntime.get(e)??{},s=this.config.options??{},i=n.cursorSessionId??this.bindingStore?.getAcpSessionId(e)??null;if(!i)return{status:"failed",message:"No Cursor chat to compact (send a message first)"};const r=String(n.cwd||s.workspace||process.cwd()),o=String(n.modelId||s.model||""),a=C(n.modeId||s.mode)??"full_auto",l=t.trim(),c=l?`${Q}
|
|
7
7
|
|
|
8
8
|
Additional focus from user:
|
|
9
|
-
${l}`:Q;this.emit("pauseIntake","compaction"),h.info("cursor-adapter",`[compact] summarize resume=${i} session=${e}`);try{const u=await this.runPrintOnce({prompt:c,resumeChatId:i,cwd:r,modelId:o||void 0,modeId:a,timeoutMs:O});if(!u.ok||!u.text.trim())return{status:"failed",message:u.error??"Summarize failed (empty result)"};const p=await this.createEmptyChat();return p?(this.sessionRuntime.set(e,{...n,cwd:r,modelId:o||n.modelId,modeId:a,cursorSessionId:p}),this.transcriptPathBySession.delete(e),this.bindingStore?.setAcpSessionId(e,p),this.identity.forgetSession(e),this.inboundMessageIds.forgetSession(e),this.pendingHandoffBySession.set(e,u.text.trim()),this.lastContextBySession.set(e,
|
|
9
|
+
${l}`:Q;this.emit("pauseIntake","compaction"),h.info("cursor-adapter",`[compact] summarize resume=${i} session=${e}`);try{const u=await this.runPrintOnce({prompt:c,resumeChatId:i,cwd:r,modelId:o||void 0,modeId:a,timeoutMs:O});if(!u.ok||!u.text.trim())return{status:"failed",message:u.error??"Summarize failed (empty result)"};const p=await this.createEmptyChat();return p?(this.sessionRuntime.set(e,{...n,cwd:r,modelId:o||n.modelId,modeId:a,cursorSessionId:p}),this.transcriptPathBySession.delete(e),this.bindingStore?.setAcpSessionId(e,p),this.identity.forgetSession(e),this.inboundMessageIds.forgetSession(e),this.pendingHandoffBySession.set(e,u.text.trim()),this.lastContextBySession.set(e,fe(0,ge(o))),this.notifyBindingReady(e),h.info("cursor-adapter",`[compact] handoff ready: old=${i} new=${p} summaryChars=${u.text.trim().length}`),{status:"ok",message:"Context compacted via summarize + new chat",data:{previousChatId:i,chatId:p}}):{status:"failed",message:"agent create-chat failed"}}finally{this.emit("resumeIntake","compaction")}}runPrintOnce(e){const t={...process.env,...this.config.env??{}},n=R(this.config.command,typeof t.PATH=="string"?t.PATH:void 0),s=[...this.config.args??[]];s.push("-p","--output-format","stream-json"),this.config.options?.trust!==!1&&s.push("--trust"),e.cwd&&s.push("--workspace",e.cwd),e.modelId&&s.push("--model",e.modelId);const i=C(e.modeId)??"full_auto";return i==="full_auto"?s.push("--yolo"):i==="plan"&&s.push("--plan"),e.resumeChatId&&s.push("--resume",e.resumeChatId),s.push(e.prompt),new Promise(r=>{let o;try{o=M(n,s,{cwd:e.cwd||process.cwd(),env:t,stdio:["ignore","pipe","pipe"]}).process}catch(m){r({ok:!1,text:"",error:`spawn failed: ${m instanceof Error?m.message:String(m)}`});return}let a="",l="",c="",u=!1;const p=e.timeoutMs&&e.timeoutMs>0?e.timeoutMs:O,v=setTimeout(()=>{u||(u=!0,this.killChildWithFallback(o),r({ok:!1,text:a,sessionId:l||void 0,error:`compact timeout after ${p}ms`}))},p);v.unref?.();const y=(m,_)=>{u||(u=!0,clearTimeout(v),r({ok:m,text:a,sessionId:l||void 0,error:_}))};$({input:o.stdout}).on("line",m=>{const _=m.trim();if(!_)return;let g;try{g=JSON.parse(_)}catch{return}if(g?.type!=="result")return;const f=typeof g?.result=="string"?g.result:"";f&&(a=f);const k=String(g?.session_id??"").trim();k&&(l=k)}),o.stderr?.on("data",m=>{c+=typeof m=="string"?m:m.toString("utf8")}),o.on("error",m=>y(!1,m.message)),o.on("close",m=>{if(!u){if(a.trim()){y(!0);return}y(!1,c.trim()||`cursor agent exited with code ${m??"unknown"}`)}})})}createEmptyChat(){const e={...process.env,...this.config.env??{}},t=R(this.config.command,typeof e.PATH=="string"?e.PATH:void 0);return new Promise(n=>{let s;try{s=M(t,["create-chat"],{cwd:process.cwd(),env:e,stdio:["ignore","pipe","pipe"]}).process}catch{n(null);return}let i="",r=!1;const o=setTimeout(()=>{r||(r=!0,this.killChildWithFallback(s),n(null))},3e4);o.unref?.(),s.stdout?.on("data",a=>{i+=typeof a=="string"?a:a.toString("utf8")}),s.on("error",()=>{r||(r=!0,clearTimeout(o),n(null))}),s.on("close",a=>{if(r)return;r=!0,clearTimeout(o);const l=i.trim().split(/\s+/)[0]??"";n(a===0&&/^[0-9a-f-]{16,}$/i.test(l)?l:null)})})}setPermissionHandler(e){this.permissionHandler=e}async ping(e){if(!this.alive||this.stopped||(this.config.options??{}).mcp_tools!==!1&&this.internalApi===null)return!1;for(const n of this.activeBySession.values()){const s=n.child?.pid;if(s&&!B(s))return!1}return!0}getStatus(){let e=0;for(const t of this.pendingBySession.values())e+=t.length;return{alive:this.alive,busy:this.activeBySession.size>0,sessions:this.sessions.size,details:{queueDepth:e+this.inboundQueue.length,activeSessions:this.activeBySession.size}}}getActiveEventIds(){const e=[];for(const t of this.activeBySession.values())t.event.event_id&&e.push(t.event.event_id);return e}clearActiveEventForShutdown(){for(const[e,t]of this.activeBySession)this.snapshotAuditBoundary(t.event.event_id,e);this.activeBySession.clear()}takeAuditBoundary(e){const t=this.completedAuditBoundaries.get(e);return t&&this.completedAuditBoundaries.delete(e),t}getMcpConfig(){return null}async probe(e){const t=this.getStatus();return{...await re(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?K(e):null;if(!t)return{recordPath:null,lastActivityMs:null,freshMs:null};try{const n=D(t);return{recordPath:t,lastActivityMs:n.mtimeMs,freshMs:Date.now()-n.mtimeMs}}catch{return{recordPath:t,lastActivityMs:null,freshMs:null}}}firstKnownCursorSessionId(){for(const e of this.activeBySession.keys()){const t=this.sessionRuntime.get(e);if(t?.cursorSessionId)return t.cursorSessionId}for(const e of this.sessionRuntime.values())if(e.cursorSessionId)return e.cursorSessionId;return null}getUsageSnapshot(e){return this.lastUsageBySession.get(e)??null}getRateLimitsSnapshot(e){const t=this.cachedAccountRateLimits,n=e?this.lastContextBySession.get(e)??null:null;if(!t)return{adapterType:"cursor",available:!1,cached:!1,sampledAt:null,rateLimits:null,contextWindow:n,tokenUsage:null};const s=Date.parse(t.sampledAt);return{adapterType:"cursor",available:t.available&&!!t.rateLimits,cached:!0,sampledAt:Number.isFinite(s)?s:Date.now(),rateLimits:t.rateLimits,contextWindow:n,tokenUsage:null,...t.displayMessage?{displayMessage:t.displayMessage}:{},...t.error?{error:t.error}:{}}}async refreshAccountRateLimits(e){const t=e?.force===!0,n=this.cachedAccountRateLimits,s=n?Date.now()-Date.parse(n.sampledAt):Number.POSITIVE_INFINITY;if(!t&&n&&Number.isFinite(s)&&s<6e4||this.rateLimitsInflight&&(await this.rateLimitsInflight,!t))return;const i=(async()=>{const r=await me();this.cachedAccountRateLimits={available:r.available,sampledAt:r.sampledAt,rateLimits:r.rateLimits,displayMessage:r.displayMessage,error:r.error},r.available&&r.rateLimits?h.info("cursor-adapter",`[rate-limits] monthly=${r.rateLimits.fiveHour.usedPercentage.toFixed(1)}% api=${r.rateLimits.sevenDay.usedPercentage.toFixed(1)}% resetsAt=${r.rateLimits.fiveHour.resetsAt}`+(r.displayMessage?` msg=${r.displayMessage}`:"")):h.warn("cursor-adapter",`[rate-limits] unavailable: ${r.error??"unknown"}`)})();this.rateLimitsInflight=i.finally(()=>{this.rateLimitsInflight=null}),await this.rateLimitsInflight}getToolbarMeta(e){const t=this.config.options??{},n=e||String(t.aibotSessionId??""),s=n?this.sessionRuntime.get(n):void 0,i=String(s?.modelId||t.model||"auto"),r=C(s?.modeId||t.mode)??"full_auto",o={model_id:i,mode_id:r,currentModelId:i,currentModeId:r,available_models:this.availableModels,available_modes:this.availableModes},a=this.cachedAccountRateLimits?.rateLimits;if(a?.fiveHour||a?.sevenDay){const c=Date.parse(this.cachedAccountRateLimits?.sampledAt??"")||Date.now();o.rate_limits={...a.fiveHour?{fiveHour:a.fiveHour}:{},...a.sevenDay?{sevenDay:a.sevenDay}:{},sampledAt:c}}const l=n?this.lastContextBySession.get(n):void 0;return l&&(o.context_window={usedPercentage:l.usedPercentage,remainingPercentage:l.remainingPercentage,usedTokens:l.usedTokens,sizeTokens:l.sizeTokens}),o}notifyBindingReady(e){if(!this.callbacks.sendUpdateBindingCard)return;const t=this.config.options??{},n=e||String(t.aibotSessionId??"");if(!n)return;const s=this.sessionRuntime.get(n),i=String(s?.cwd||t.workspace||"");i&&this.callbacks.sendUpdateBindingCard(n,this.activeBySession.has(n)?"busy":"ready",i,this.getToolbarMeta(n))}buildPromptText(e){let t;!e.contextMessages||e.contextMessages.length===0?t=e.text:t=`${e.contextMessages.map(r=>`[${r.senderId}] ${r.content}`).join(`
|
|
10
10
|
`)}
|
|
11
11
|
|
|
12
12
|
[Current user message]
|
|
@@ -18,6 +18,6 @@ ${t}`);const s=this.pendingHandoffBySession.get(e.adapterSessionId);return s&&(t
|
|
|
18
18
|
${s}
|
|
19
19
|
|
|
20
20
|
[Current user message]
|
|
21
|
-
${t}`),this.identity.injectOnce(e.adapterSessionId,t)}handleStdoutLineForActive(e,t){if(e.done)return;const n=t.trim();if(!n)return;e.lastIoAt=Date.now();const{event:s}=e;let i;try{i=JSON.parse(n)}catch{this.armActiveIdleTimer(e.request.adapterSessionId);return}const r=this.auditCaptures.get(s.event_id);if(r&&i&&typeof i=="object"&&r.push({sequence:r.length,observedAt:new Date().toISOString(),event:structuredClone(i)}),i?.type==="tool_call"){const l=String(i?.call_id??""),c=String(i?.subtype??"");l&&(c==="started"||c==="start"?(this.flushAssistantStreamBuffer(e),e.toolCallsInFlight.add(l),e.toolCallsStarted+=1,this.handleSpecialToolCallStarted(e,i)):U.has(c)&&e.toolCallsInFlight.delete(l))}this.armActiveIdleTimer(e.request.adapterSessionId);const o=ve(i);o&&this.callbacks.sendRawEventEnvelope?.(s.event_id,s.session_id,o);const a=Ae(i,e.assistantTextEmitted);if(a&&this.enqueueAssistantStreamDelta(e,a),i?.type==="result"){e.sawResult=!0;const l=typeof i?.result=="string"?i.result.trim():"";l&&(e.assistantTextEmitted?l.startsWith(e.assistantTextEmitted)&&l.length>e.assistantTextEmitted.length&&this.enqueueAssistantStreamDelta(e,l.slice(e.assistantTextEmitted.length)):this.enqueueAssistantStreamDelta(e,l)),this.flushAssistantStreamBuffer(e);const c=String(i?.session_id??"").trim();if(c){const f=this.sessionRuntime.get(e.request.adapterSessionId)??{};f.cursorSessionId!==c&&this.transcriptPathBySession.delete(e.request.adapterSessionId),this.sessionRuntime.set(e.request.adapterSessionId,{...f,cursorSessionId:c}),this.bindingStore?.setAcpSessionId(e.request.adapterSessionId,c)}const u=i?.usage??{},p=this.lastUsageBySession.get(e.request.adapterSessionId);this.lastUsageBySession.set(e.request.adapterSessionId,{sampledAt:new Date().toISOString(),turns:(p?.turns??0)+1,total:{input:(p?.total.input??0)+Number(u.inputTokens??0),output:(p?.total.output??0)+Number(u.outputTokens??0),cacheRead:(p?.total.cacheRead??0)+Number(u.cacheReadTokens??0),cacheWrite:(p?.total.cacheWrite??0)+Number(u.cacheWriteTokens??0)}});const v=String((this.sessionRuntime.get(e.request.adapterSessionId)?.modelId??this.config.options?.model??"")||""),y=Math.max(1,(e.toolCallsStarted??0)+1),b=Number(u.inputTokens??0),m=Number(u.cacheReadTokens??0),_=Number(u.cacheWriteTokens??0),g=he({inputTokens:b,cacheReadTokens:m,cacheWriteTokens:_},v,{apiRounds:y});g&&(this.lastContextBySession.set(e.request.adapterSessionId,g),h.info("cursor-adapter",`[context_window] used=${g.usedTokens} size=${g.sizeTokens} pct=${g.usedPercentage.toFixed(1)}% model=${v||"auto"} rawIn=${b} rawCache=${m} rawWrite=${_} apiRounds=${y}`))}}handleSpecialToolCallStarted(e,t){const n=W(t);if(J(n.toolName)){const s=_e(n.toolInput);s?(e.seq+=1,this.callbacks.sendStreamChunk(e.event.event_id,e.event.session_id,s,e.seq,!1),h.info("cursor-adapter",`CreatePlan forwarded as stream text: event=${e.event.event_id} chars=${s.length}`)):(e.seq+=1,this.callbacks.sendStreamChunk(e.event.event_id,e.event.session_id,"[CreatePlan] \u65B9\u6848\u5185\u5BB9\u89E3\u6790\u5931\u8D25\uFF0C\u672A\u4E0B\u53D1\u6B63\u6587\u3002",e.seq,!1),h.warn("cursor-adapter",`CreatePlan missing plan body: event=${e.event.event_id}`));return}z(n.toolName)&&this.emitAskQuestionCard(e,n.toolInput)}emitAskQuestionCard(e,t){const n=e.event,s=ye(t);if(!s){e.seq+=1,this.callbacks.sendStreamChunk(n.event_id,n.session_id,"[AskQuestion] \u95EE\u9898\u5185\u5BB9\u89E3\u6790\u5931\u8D25\uFF0C\u672A\u4E0B\u53D1\u95EE\u9898\u5361\u3002",e.seq,!1),h.warn("cursor-adapter",`AskQuestion parse failed: event=${n.event_id}`);return}if(!this.callbacks.sendAgentQuestionCard){h.warn("cursor-adapter","AskQuestion skipped: sendAgentQuestionCard callback not wired");return}const i=Y(),r=Date.now();this.pendingQuestionByRequestId.set(i,{eventId:n.event_id,sessionId:n.session_id,createdAt:r}),this.callbacks.sendAgentQuestionCard(n.event_id,n.session_id,{request_id:i,mode:"form",questions:s,expires_at:r+T}),h.info("cursor-adapter",`Sent agent_question card: request_id=${i} questions=${s.length} event=${n.event_id}`)}async handleQuestionReply(e){const t=e.params??{},n=String(t.request_id??"").trim();if(!n)return this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"request_id_required","request_id is required"),{handled:!0,kind:"question_reply_no_id"};const s=this.pendingQuestionByRequestId.get(n),i=s?Date.now()-s.createdAt>T+3e4:!0;if(!s||i)return this.pendingQuestionByRequestId.delete(n),this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"interaction_request_not_pending","The question is no longer pending; the reply was not delivered."),{handled:!0,kind:"question_reply_not_pending"};const r=t.resolution??{},o=String(r.type??"");if(o==="action"&&String(r.value??"")==="cancel")return this.pendingQuestionByRequestId.delete(n),this.callbacks.sendLocalActionResult(e.action_id,"ok",{request_id:n,resolution:"cancel"}),{handled:!0,kind:"question_reply_cancel"};let a="";return o==="text"?a=String(r.value??""):o==="map"&&(a=(Array.isArray(r.entries)?r.entries:[]).map(c=>c.value).join(", ")),a?(this.pendingQuestionByRequestId.delete(n),this.pendingQuestionAnswerBySession.set(s.sessionId,a),this.callbacks.sendLocalActionResult(e.action_id,"ok",{request_id:n,resolution:"answer"}),h.info("cursor-adapter",`Question reply cached for next prompt: request_id=${n} session=${s.sessionId}`),{handled:!0,kind:"question_reply"}):(this.pendingQuestionByRequestId.delete(n),this.callbacks.sendLocalActionResult(e.action_id,"ok",{request_id:n,resolution:"cancel"}),{handled:!0,kind:"question_reply_empty"})}enqueueAssistantStreamDelta(e,t){t&&(e.assistantTextEmitted+=t,e.streamBuffer+=t,e.streamBuffer.length>=pe&&this.flushAssistantStreamBuffer(e))}flushAssistantStreamBuffer(e){if(!e.streamBuffer)return;const t=e.streamBuffer;e.streamBuffer="",e.seq+=1,this.callbacks.sendStreamChunk(e.event.event_id,e.event.session_id,t,e.seq,!1,void 0,void 0)}shouldExtendByLiveness(e){const t=this.activeBySession.get(e);if(!t||t.done)return!1;const n=t.child?.pid;if(!n||!B(n)||t.sawResult)return!1;const s=Date.now(),i=s-t.lastIoAt,r=this.transcriptFreshMsForSession(e);if(i<F||r!==null&&r<F)return t.livenessExtendStartAt=s,h.info("cursor-adapter",`Liveness check: fresh progress for ${t.event.event_id} (ioAge=${i}ms, transcriptAge=${r??"n/a"}, toolsInFlight=${t.toolCallsInFlight.size}), extending`),!0;const a=t.livenessExtendStartAt??s;return t.livenessExtendStartAt===void 0&&(t.livenessExtendStartAt=a),s-a>j?(h.warn("cursor-adapter",`Liveness extension budget exhausted for ${t.event.event_id} (no progress for ${Math.round((s-a)/6e4)}min), allowing idle kill`),!1):(h.info("cursor-adapter",`Liveness check: turn in progress for ${t.event.event_id} (toolsInFlight=${t.toolCallsInFlight.size}, ioAge=${i}ms), extending`),!0)}transcriptFreshMsForSession(e){const t=this.sessionRuntime.get(e)?.cursorSessionId;if(!t)return null;let n=this.transcriptPathBySession.get(e);if(!n){const s=K(t);if(!s)return null;this.transcriptPathBySession.set(e,s),n=s}try{return Date.now()-D(n).mtimeMs}catch{return this.transcriptPathBySession.delete(e),null}}hasPendingQuestionForSession(e){const t=Date.now();for(const[n,s]of this.pendingQuestionByRequestId)if(s.sessionId===e){if(t-s.createdAt>T){this.pendingQuestionByRequestId.delete(n);continue}return!0}return!1}armActiveIdleTimer(e){const t=this.activeBySession.get(e);!t||t.done||(t.idleTimer&&clearTimeout(t.idleTimer),t.idleTimer=setTimeout(()=>{if(t.done)return;if(this.hasPendingQuestionForSession(e)){h.info("cursor-adapter",`Idle timeout skipped: awaiting AskQuestion for ${t.event.event_id}, resetting timer`),this.armActiveIdleTimer(e);return}if(this.shouldExtendByLiveness(e)){this.armActiveIdleTimer(e);return}if(t.sawResult){h.warn("cursor-adapter",`Idle after result with no close \u2014 finishing as responded: event=${t.event.event_id} session=${e}`),this.killChildWithFallback(t.child),this.finishActive(e,"responded");return}const n=t.child?.pid,i=!!(n&&B(n))?`liveness budget exhausted after ${j/6e4}min`:"child process dead";h.error("cursor-adapter",`Idle timeout (${i}, toolsInFlight=${t.toolCallsInFlight.size}) \u2014 killing cursor child: event=${t.event.event_id} session=${e}`),this.killChildWithFallback(t.child),this.finishActive(e,"failed",`idle timeout: ${i}`)},fe),t.idleTimer.unref?.())}finishActive(e,t,n){const s=this.activeBySession.get(e);if(!s)return;s.done=!0,s.timer&&clearTimeout(s.timer),s.idleTimer&&clearTimeout(s.idleTimer),this.flushAssistantStreamBuffer(s),s.seq+=1;const i=String(s.event.quoted_message_id??"").trim()||void 0;this.callbacks.sendStreamChunk(s.event.event_id,s.event.session_id,"",s.seq,!0,void 0,i),h.info("cursor-adapter",`job finish: event=${s.event.event_id} session=${s.event.session_id} status=${t}${n?` msg=${n}`:""}`),this.snapshotAuditBoundary(s.event.event_id,e),this.emit("eventDone",s.event.event_id),this.callbacks.sendEventResult(s.event.event_id,t,n),t==="responded"?s.handle.emitDone({status:"completed"}):t==="canceled"?s.handle.emitDone({status:"canceled",error:n}):s.handle.emitDone({status:"failed",error:n}),this.activeBySession.delete(e),this.notifyBindingReady(e),this.tryStartNext(e)}snapshotAuditBoundary(e,t){const n=this.auditCaptures.get(e);if(!n)return;const s=this.sessionRuntime.get(t);this.completedAuditBoundaries.set(e,{adapterType:"cursor",providerSessionId:s?.cursorSessionId,cursorEvidence:{events:n}}),this.auditCaptures.delete(e)}shouldRetryWithoutContinue(e){if(!e.usedContinue||e.retryCount>0)return!1;const t=e.stderr.toLowerCase(),n=t.includes("resume")||t.includes("continue")||t.includes("chat")||t.includes("session"),s=t.includes("not found")||t.includes("no previous session")||t.includes("does not exist")||t.includes("invalid");return n&&s}ensureWorkspaceMcpAndSkills(e){if(!this.internalApi)return;this.ensureCursorMcpConfig(e,this.internalApi.url);const t=A(N(),".cursor","skills"),n=ae(t);n.length>0&&h.info("cursor-adapter",`Synced connector skills to ${t}: [${n.join(", ")}]`)}ensureCursorMcpConfig(e,t){try{const n=A(e,".cursor"),s=A(n,"mcp.json");E(n,{recursive:!0});let i={};try{i=JSON.parse(L(s,"utf8"))}catch{i={}}const r={command:process.execPath,args:[ee(te(import.meta.url),"../../../mcp/acp-mcp-server.js"),"--api-url",t]};(!i.mcpServers||typeof i.mcpServers!="object")&&(i.mcpServers={}),i.mcpServers.grix=r,P(s,`${JSON.stringify(i,null,2)}
|
|
22
|
-
`,"utf8"),
|
|
23
|
-
`,"utf8")}catch(s){h.warn("cursor-adapter",`Failed to record MCP registry: ${String(s)}`)}}}export{G as CURSOR_MODE_OPTIONS,x as CursorAdapter,
|
|
21
|
+
${t}`),this.identity.injectOnce(e.adapterSessionId,t)}handleStdoutLineForActive(e,t){if(e.done)return;const n=t.trim();if(!n)return;e.lastIoAt=Date.now();const{event:s}=e;let i;try{i=JSON.parse(n)}catch{this.armActiveIdleTimer(e.request.adapterSessionId);return}const r=this.auditCaptures.get(s.event_id);if(r&&i&&typeof i=="object"&&r.push({sequence:r.length,observedAt:new Date().toISOString(),event:structuredClone(i)}),i?.type==="tool_call"){const l=String(i?.call_id??""),c=String(i?.subtype??"");l&&(c==="started"||c==="start"?(this.flushAssistantStreamBuffer(e),e.toolCallsInFlight.add(l),e.toolCallsStarted+=1,this.handleSpecialToolCallStarted(e,i)):W.has(c)&&e.toolCallsInFlight.delete(l))}this.armActiveIdleTimer(e.request.adapterSessionId);const o=ke(i);o&&this.callbacks.sendRawEventEnvelope?.(s.event_id,s.session_id,o);const a=we(i,e.assistantTextEmitted);if(a&&this.enqueueAssistantStreamDelta(e,a),i?.type==="result"){e.sawResult=!0;const l=typeof i?.result=="string"?i.result.trim():"";l&&(e.assistantTextEmitted?l.startsWith(e.assistantTextEmitted)&&l.length>e.assistantTextEmitted.length&&this.enqueueAssistantStreamDelta(e,l.slice(e.assistantTextEmitted.length)):this.enqueueAssistantStreamDelta(e,l)),this.flushAssistantStreamBuffer(e);const c=String(i?.session_id??"").trim();if(c){const f=this.sessionRuntime.get(e.request.adapterSessionId)??{};f.cursorSessionId!==c&&this.transcriptPathBySession.delete(e.request.adapterSessionId),this.sessionRuntime.set(e.request.adapterSessionId,{...f,cursorSessionId:c}),this.bindingStore?.setAcpSessionId(e.request.adapterSessionId,c)}const u=i?.usage??{},p=this.lastUsageBySession.get(e.request.adapterSessionId);this.lastUsageBySession.set(e.request.adapterSessionId,{sampledAt:new Date().toISOString(),turns:(p?.turns??0)+1,total:{input:(p?.total.input??0)+Number(u.inputTokens??0),output:(p?.total.output??0)+Number(u.outputTokens??0),cacheRead:(p?.total.cacheRead??0)+Number(u.cacheReadTokens??0),cacheWrite:(p?.total.cacheWrite??0)+Number(u.cacheWriteTokens??0)}});const v=String((this.sessionRuntime.get(e.request.adapterSessionId)?.modelId??this.config.options?.model??"")||""),y=Math.max(1,(e.toolCallsStarted??0)+1),b=Number(u.inputTokens??0),m=Number(u.cacheReadTokens??0),_=Number(u.cacheWriteTokens??0),g=pe({inputTokens:b,cacheReadTokens:m,cacheWriteTokens:_},v,{apiRounds:y});g&&(this.lastContextBySession.set(e.request.adapterSessionId,g),h.info("cursor-adapter",`[context_window] used=${g.usedTokens} size=${g.sizeTokens} pct=${g.usedPercentage.toFixed(1)}% model=${v||"auto"} rawIn=${b} rawCache=${m} rawWrite=${_} apiRounds=${y}`))}}handleSpecialToolCallStarted(e,t){const n=U(t);if(J(n.toolName)){const s=be(n.toolInput);s?(e.seq+=1,this.callbacks.sendStreamChunk(e.event.event_id,e.event.session_id,s,e.seq,!1),h.info("cursor-adapter",`CreatePlan forwarded as stream text: event=${e.event.event_id} chars=${s.length}`)):(e.seq+=1,this.callbacks.sendStreamChunk(e.event.event_id,e.event.session_id,"[CreatePlan] \u65B9\u6848\u5185\u5BB9\u89E3\u6790\u5931\u8D25\uFF0C\u672A\u4E0B\u53D1\u6B63\u6587\u3002",e.seq,!1),h.warn("cursor-adapter",`CreatePlan missing plan body: event=${e.event.event_id}`));return}z(n.toolName)&&this.emitAskQuestionCard(e,n.toolInput)}emitAskQuestionCard(e,t){const n=e.event,s=Ae(t);if(!s){e.seq+=1,this.callbacks.sendStreamChunk(n.event_id,n.session_id,"[AskQuestion] \u95EE\u9898\u5185\u5BB9\u89E3\u6790\u5931\u8D25\uFF0C\u672A\u4E0B\u53D1\u95EE\u9898\u5361\u3002",e.seq,!1),h.warn("cursor-adapter",`AskQuestion parse failed: event=${n.event_id}`);return}if(!this.callbacks.sendAgentQuestionCard){h.warn("cursor-adapter","AskQuestion skipped: sendAgentQuestionCard callback not wired");return}const i=Z(),r=Date.now();this.pendingQuestionByRequestId.set(i,{eventId:n.event_id,sessionId:n.session_id,createdAt:r}),this.callbacks.sendAgentQuestionCard(n.event_id,n.session_id,{request_id:i,mode:"form",questions:s,expires_at:r+T}),h.info("cursor-adapter",`Sent agent_question card: request_id=${i} questions=${s.length} event=${n.event_id}`)}async handleQuestionReply(e){const t=e.params??{},n=String(t.request_id??"").trim();if(!n)return this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"request_id_required","request_id is required"),{handled:!0,kind:"question_reply_no_id"};const s=this.pendingQuestionByRequestId.get(n),i=s?Date.now()-s.createdAt>T+3e4:!0;if(!s||i)return this.pendingQuestionByRequestId.delete(n),this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"interaction_request_not_pending","The question is no longer pending; the reply was not delivered."),{handled:!0,kind:"question_reply_not_pending"};const r=t.resolution??{},o=String(r.type??"");if(o==="action"&&String(r.value??"")==="cancel")return this.pendingQuestionByRequestId.delete(n),this.callbacks.sendLocalActionResult(e.action_id,"ok",{request_id:n,resolution:"cancel"}),{handled:!0,kind:"question_reply_cancel"};let a="";return o==="text"?a=String(r.value??""):o==="map"&&(a=(Array.isArray(r.entries)?r.entries:[]).map(c=>c.value).join(", ")),a?(this.pendingQuestionByRequestId.delete(n),this.pendingQuestionAnswerBySession.set(s.sessionId,a),this.callbacks.sendLocalActionResult(e.action_id,"ok",{request_id:n,resolution:"answer"}),h.info("cursor-adapter",`Question reply cached for next prompt: request_id=${n} session=${s.sessionId}`),{handled:!0,kind:"question_reply"}):(this.pendingQuestionByRequestId.delete(n),this.callbacks.sendLocalActionResult(e.action_id,"ok",{request_id:n,resolution:"cancel"}),{handled:!0,kind:"question_reply_empty"})}enqueueAssistantStreamDelta(e,t){t&&(e.assistantTextEmitted+=t,e.streamBuffer+=t,e.streamBuffer.length>=ye&&this.flushAssistantStreamBuffer(e))}flushAssistantStreamBuffer(e){if(!e.streamBuffer)return;const t=e.streamBuffer;e.streamBuffer="",e.seq+=1,this.callbacks.sendStreamChunk(e.event.event_id,e.event.session_id,t,e.seq,!1,void 0,void 0)}shouldExtendByLiveness(e){const t=this.activeBySession.get(e);if(!t||t.done)return!1;const n=t.child?.pid;if(!n||!B(n)||t.sawResult)return!1;const s=Date.now(),i=s-t.lastIoAt,r=this.transcriptFreshMsForSession(e);if(i<F||r!==null&&r<F)return t.livenessExtendStartAt=s,h.info("cursor-adapter",`Liveness check: fresh progress for ${t.event.event_id} (ioAge=${i}ms, transcriptAge=${r??"n/a"}, toolsInFlight=${t.toolCallsInFlight.size}), extending`),!0;const a=t.livenessExtendStartAt??s;return t.livenessExtendStartAt===void 0&&(t.livenessExtendStartAt=a),s-a>j?(h.warn("cursor-adapter",`Liveness extension budget exhausted for ${t.event.event_id} (no progress for ${Math.round((s-a)/6e4)}min), allowing idle kill`),!1):(h.info("cursor-adapter",`Liveness check: turn in progress for ${t.event.event_id} (toolsInFlight=${t.toolCallsInFlight.size}, ioAge=${i}ms), extending`),!0)}transcriptFreshMsForSession(e){const t=this.sessionRuntime.get(e)?.cursorSessionId;if(!t)return null;let n=this.transcriptPathBySession.get(e);if(!n){const s=K(t);if(!s)return null;this.transcriptPathBySession.set(e,s),n=s}try{return Date.now()-D(n).mtimeMs}catch{return this.transcriptPathBySession.delete(e),null}}hasPendingQuestionForSession(e){const t=Date.now();for(const[n,s]of this.pendingQuestionByRequestId)if(s.sessionId===e){if(t-s.createdAt>T){this.pendingQuestionByRequestId.delete(n);continue}return!0}return!1}armActiveIdleTimer(e){const t=this.activeBySession.get(e);!t||t.done||(t.idleTimer&&clearTimeout(t.idleTimer),t.idleTimer=setTimeout(()=>{if(t.done)return;if(this.hasPendingQuestionForSession(e)){h.info("cursor-adapter",`Idle timeout skipped: awaiting AskQuestion for ${t.event.event_id}, resetting timer`),this.armActiveIdleTimer(e);return}if(this.shouldExtendByLiveness(e)){this.armActiveIdleTimer(e);return}if(t.sawResult){h.warn("cursor-adapter",`Idle after result with no close \u2014 finishing as responded: event=${t.event.event_id} session=${e}`),this.killChildWithFallback(t.child),this.finishActive(e,"responded");return}const n=t.child?.pid,i=!!(n&&B(n))?`liveness budget exhausted after ${j/6e4}min`:"child process dead";h.error("cursor-adapter",`Idle timeout (${i}, toolsInFlight=${t.toolCallsInFlight.size}) \u2014 killing cursor child: event=${t.event.event_id} session=${e}`),this.killChildWithFallback(t.child),this.finishActive(e,"failed",`idle timeout: ${i}`)},Se),t.idleTimer.unref?.())}finishActive(e,t,n){const s=this.activeBySession.get(e);if(!s)return;s.done=!0,s.timer&&clearTimeout(s.timer),s.idleTimer&&clearTimeout(s.idleTimer),this.flushAssistantStreamBuffer(s),s.seq+=1;const i=String(s.event.quoted_message_id??"").trim()||void 0;this.callbacks.sendStreamChunk(s.event.event_id,s.event.session_id,"",s.seq,!0,void 0,i),h.info("cursor-adapter",`job finish: event=${s.event.event_id} session=${s.event.session_id} status=${t}${n?` msg=${n}`:""}`),this.snapshotAuditBoundary(s.event.event_id,e),this.emit("eventDone",s.event.event_id),this.callbacks.sendEventResult(s.event.event_id,t,n),t==="responded"?s.handle.emitDone({status:"completed"}):t==="canceled"?s.handle.emitDone({status:"canceled",error:n}):s.handle.emitDone({status:"failed",error:n}),this.activeBySession.delete(e),this.notifyBindingReady(e),this.tryStartNext(e)}snapshotAuditBoundary(e,t){const n=this.auditCaptures.get(e);if(!n)return;const s=this.sessionRuntime.get(t);this.completedAuditBoundaries.set(e,{adapterType:"cursor",providerSessionId:s?.cursorSessionId,cursorEvidence:{events:n}}),this.auditCaptures.delete(e)}shouldRetryWithoutContinue(e){if(!e.usedContinue||e.retryCount>0)return!1;const t=e.stderr.toLowerCase(),n=t.includes("resume")||t.includes("continue")||t.includes("chat")||t.includes("session"),s=t.includes("not found")||t.includes("no previous session")||t.includes("does not exist")||t.includes("invalid");return n&&s}ensureWorkspaceMcpAndSkills(e){if(!this.internalApi)return;this.ensureCursorMcpConfig(e,this.internalApi.url);const t=A(N(),".cursor","skills"),n=ce(t);n.length>0&&h.info("cursor-adapter",`Synced connector skills to ${t}: [${n.join(", ")}]`)}ensureCursorMcpConfig(e,t){const n=ae(this.callbacks.getAgentId?.()),s={command:process.execPath,args:[te(se(import.meta.url),"../../../mcp/acp-mcp-server.js"),"--api-url",t]},i=[e],r=this.resolveGitRoot(e);r&&r!==e&&i.push(r);let o=null;for(const a of i){const l=this.writeCursorMcpConfig(a,n,s,t);!o&&l&&(o=l)}o&&this.recordCursorMcpRegistry(e,o,s)}resolveGitRoot(e){try{return Y("git",["-C",e,"rev-parse","--show-toplevel"],{timeout:5e3,stdio:["ignore","pipe","ignore"]}).toString("utf8").trim()||null}catch{return null}}writeCursorMcpConfig(e,t,n,s){try{const i=A(e,".cursor"),r=A(i,"mcp.json");E(i,{recursive:!0});let o={};try{o=JSON.parse(L(r,"utf8"))}catch{o={}}(!o.mcpServers||typeof o.mcpServers!="object")&&(o.mcpServers={});const a=de(o.mcpServers,s);return o.mcpServers[t]=n,P(r,`${JSON.stringify(o,null,2)}
|
|
22
|
+
`,"utf8"),h.info("cursor-adapter",`MCP config synced: dir=${e} file=${r} server=${t}${a.length>0?` pruned=${a.join(",")}`:""}`),r}catch(i){return h.warn("cursor-adapter",`Failed to ensure ${A(e,".cursor","mcp.json")}: ${String(i)}`),null}}recordCursorMcpRegistry(e,t,n){try{const s=A(ne.data,"cursor"),i=A(s,"mcp-registry.json");E(s,{recursive:!0});let r={};try{r=JSON.parse(L(i,"utf8"))}catch{r={}}r[e]={mcp_path:t,server:n,updated_at:new Date().toISOString()},P(i,`${JSON.stringify(r,null,2)}
|
|
23
|
+
`,"utf8")}catch(s){h.warn("cursor-adapter",`Failed to record MCP registry: ${String(s)}`)}}}export{G as CURSOR_MODE_OPTIONS,x as CursorAdapter,we as extractCursorAssistantStreamDelta,K as findCursorTranscript,be as formatCursorCreatePlanText,z as isCursorAskQuestionTool,J as isCursorCreatePlanTool,ke as mapCursorStreamJsonToRawEnvelope,Ae as parseCursorAskQuestionPayload};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import{EventEmitter as _}from"node:events";import{stat as E}from"node:fs/promises";import{existsSync as M,mkdirSync as $,readFileSync as O,writeFileSync as F}from"node:fs";import{join as
|
|
2
|
-
`,"utf8"),r.info("opencode-adapter",`MCP config injected into ${t}`);const o=
|
|
3
|
-
`)){const T=g.match(/opencode server listening on (https?:\/\/[^\s]+)/);if(T){clearTimeout(I),u=!0,this.alive=!0,i(T[1]);return}}}),this.process.stderr?.on("data",l=>{const g=l.toString().trim();g&&r.info("opencode-adapter",`[stderr] ${g}`)}),this.process.on("error",l=>{r.error("opencode-adapter",`Spawn error: ${l.message}`),clearTimeout(I),this.alive=!1,this.transport.disconnect(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed",`Spawn error: ${l.message}`),this.clearRun()),u?this.stopped||this.emit("exit",1):(u=!0,n(l))}),this.process.on("exit",l=>{r.info("opencode-adapter",`Process exited (code=${l})`),clearTimeout(I),this.alive=!1,this.transport.disconnect(),this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed",`Process exited (code=${l})`),this.clearRun()),u?this.stopped||this.emit("exit",l??1):(u=!0,n(new Error(`opencode serve exited with code ${l}`)))})})}async ensureOcSession(e){const s=this.sessions.get(e),t=this.getSessionCwd(e);if(await this.transport.useDirectory(t),s?.ocSessionId)try{return await this.transport.getSession(s.ocSessionId,t),s.ocSessionId}catch{r.warn("opencode-adapter",`OC session ${s.ocSessionId} gone, creating new`),s.ocSessionId=""}const i=this.options.bindingStore?.getAcpSessionId(e);if(i)try{return await this.transport.getSession(i,t),this.sessions.set(e,{ocSessionId:i,cwd:t}),r.info("opencode-adapter",`Resumed OC session ${i} for aibot=${e}`),await this.applySessionPermission(e,i,t),i}catch{r.warn("opencode-adapter",`Persisted OC session ${i} gone, creating new`)}const n=await this.transport.createSession({title:`grix-${e.slice(-8)}`},t);return this.sessions.set(e,{ocSessionId:n.id,cwd:t}),this.options.bindingStore?.setAcpSessionId(e,n.id),r.info("opencode-adapter",`Created OC session ${n.id} for aibot=${e}`),await this.applySessionPermission(e,n.id,t),n.id}async sendOcPrompt(e,s){const t=this.getSessionCwd(e),i=await this.ensureOcSession(e);this.activeRun&&(this.activeRun.ocSessionId=i,this.activeRun.cwd=t);const n=this.resolveModelId(e),o=n.indexOf("/"),a=o>0&&o<n.length-1?{providerID:n.slice(0,o),modelID:n.slice(o+1)}:void 0;await this.transport.sendPromptAsync(i,{parts:[{type:"text",text:s}],...a?{model:a}:{}},t)}handleSseEvent(e){if(!this.stopped){if(this.updateToolInFlight(e),e.type!=="server.heartbeat"&&(this.lastSseAt=Date.now(),this.resetIdleTimer()),this.activeRun){const s=this.auditCaptures.get(this.activeRun.eventId);s&&s.push({sequence:s.length,observedAt:new Date().toISOString(),event:structuredClone(e)})}switch(e.type){case"message.part.updated":{if(!this.activeRun)break;const s=e.part,t=e.delta;if(s.type==="text"){const i=t||s.text;i&&this.acceptAssistantPartText(s.messageID,"text",i)}else s.type==="reasoning"?t&&this.acceptAssistantPartText(s.messageID,"reasoning",t):s.type==="tool"&&this.handleToolPartUpdate(s,t);break}case"message.updated":{if(!this.activeRun)break;const s=e.info;if(this.rememberMessageRole(s),s.role==="assistant"){const t=s;typeof t.tokens?.input=="number"&&this.contextTokensBySession.set(this.activeRun.sessionId,t.tokens.input);const i=this.buildContextWindowMeta(this.activeRun.sessionId,t.providerID,t.modelID,t.tokens?.input);this.callbacks.sendUpdateBindingCard(this.activeRun.sessionId,"composing",this.activeRun.cwd,{...this.buildToolbarMeta(this.activeRun.sessionId),model_provider:t.providerID,model_id:t.modelID,...i??{}}),t.error&&r.warn("opencode-adapter",`Message error: ${JSON.stringify(t.error)}`);const n=this.activeRun.ocSessionId;n&&t.sessionID===n&&(t.error?(this.flushTextBuffer(),this.finishRun("failed",k(t.error,t.error.type||"unknown error"))):t.time?.completed&&t.finish!=="tool-calls"&&(this.flushTextBuffer(),this.finishRun("responded")))}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=k(e.error);r.error("opencode-adapter",`Session error: ${s}`),this.flushTextBuffer(),this.finishRun("failed",s);break}case"permission.updated":{this.handlePermission(e.permission);break}case"permission.asked":{this.handlePermissionAsked(e);break}case"question.asked":{this.handleQuestionAsked(e);break}case"permission.replied":case"question.replied":case"question.rejected":r.debug("opencode-adapter",`Interaction settled: ${e.type}`);break;case"session.created":case"session.updated":case"session.deleted":case"session.compacted":case"session.diff":case"file.edited":case"server.connected":case"server.heartbeat":break;case"server.instance.disposed":{r.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:r.debug("opencode-adapter",`Unhandled SSE event type: ${e.type}`);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)}rememberMessageRole(e){const s=typeof e.id=="string"?e.id.trim():"";s&&(e.role!=="user"&&e.role!=="assistant"||(this.messageRoles.set(s,e.role),this.flushOrDropPendingParts(s,e.role)))}acceptAssistantPartText(e,s,t){if(!this.activeRun||!t)return;const i=typeof e=="string"?e.trim():"";if(!i)return;const n=this.messageRoles.get(i);if(n==="user")return;if(n==="assistant"){this.emitAssistantPartChunk(s,t);return}const o=this.pendingPartChunks.get(i)??[];o.push({kind:s,value:t}),this.pendingPartChunks.set(i,o)}flushOrDropPendingParts(e,s){const t=this.pendingPartChunks.get(e);if(this.pendingPartChunks.delete(e),!(!t||t.length===0)&&s==="assistant")for(const i of t)this.emitAssistantPartChunk(i.kind,i.value)}emitAssistantPartChunk(e,s){if(!(!this.activeRun||!s)){if(e==="reasoning"){this.callbacks.sendThinking(this.activeRun.eventId,this.activeRun.sessionId,s);return}this.appendText(s)}}clearMessageRoleState(){this.messageRoles.clear(),this.pendingPartChunks.clear()}handleToolPartUpdate(e,s){if(!this.activeRun)return;const t=e.state;switch(t.status){case"pending":case"running":{this.sendToolUseOnce(e,t.input);break}case"completed":{this.sendToolUseOnce(e,t.input);break}case"error":{this.sendToolUseOnce(e,t.input),this.callbacks.sendToolResult(this.activeRun.eventId,this.activeRun.sessionId,e.tool,"(failed)");break}}}sendToolUseOnce(e,s){!this.activeRun||this.toolCardsSent.has(e.callID)||(this.flushTextBuffer(),this.toolCardsSent.add(e.callID),this.callbacks.sendToolUse(this.activeRun.eventId,this.activeRun.sessionId,e.tool,G(s??{})))}onPermissionTimeout(e){const s=this.pendingPermissions.get(e);s&&(this.pendingPermissions.delete(e),r.error("opencode-adapter",`Permission approval timeout: ${e}`),this.transport.respondPermission(s.ocSessionId,e,"reject",s.cwd).catch(()=>{}),this.activeRun&&this.activeRun.eventId===s.eventId&&(this.flushTextBuffer(),this.finishRun("failed","permission approval timeout")))}trackPendingPermission(e){if(!this.activeRun)return;this.stopIdleTimer();const s=setTimeout(()=>this.onPermissionTimeout(e),S);this.pendingPermissions.set(e,{permissionId:e,eventId:this.activeRun.eventId,ocSessionId:this.activeRun.ocSessionId,cwd:this.activeRun.cwd,timer:s})}handlePermissionAsked(e){if(!this.activeRun){this.transport.respondPermission(e.sessionID,e.id,"reject").catch(o=>{r.warn("opencode-adapter",`Orphan permission reject failed: ${o}`)});return}const{eventId:s,sessionId:t,ocSessionId:i}=this.activeRun;if(this.resolveModeId(t)===m.fullAuto){this.transport.respondPermission(i,e.id,"always",this.activeRun.cwd).catch(o=>{r.warn("opencode-adapter",`Auto-approve failed: ${o}`)});return}this.trackPendingPermission(e.id);const n=e.patterns.length>0?`${e.permission}: ${e.patterns.join(", ")}`:e.permission;this.callbacks.sendPermissionCard?this.callbacks.sendPermissionCard({eventId:s,sessionId:t,approvalId:e.id,toolName:e.permission,toolTitle:n,toolInput:JSON.stringify(e.metadata??{})}):this.callbacks.sendToolUse(s,t,e.permission,JSON.stringify(e.metadata??{}))}handlePermission(e){if(!this.activeRun)return;const{eventId:s,sessionId:t,ocSessionId:i}=this.activeRun;if(this.resolveModeId(t)===m.fullAuto){this.transport.respondPermission(i,e.id,"always",this.activeRun.cwd).catch(n=>{r.warn("opencode-adapter",`Auto-approve failed: ${n}`)});return}if(this.trackPendingPermission(e.id),this.callbacks.sendPermissionCard)this.callbacks.sendPermissionCard({eventId:s,sessionId:t,approvalId:e.id,toolName:e.type,toolTitle:e.title||e.type,toolInput:JSON.stringify(e.metadata??{})});else{const n=JSON.stringify(e.metadata??{});this.callbacks.sendToolUse(s,t,e.type,n)}}handlePermissionAction(e){if(!this.activeRun)return{handled:!1,kind:""};const s=e.params??{},t=String(s.approval_id??s.approval_command_id??s.approvalId??s.tool_call_id??"");let i=t?this.pendingPermissions.get(t):void 0;if(!i){for(const a of this.pendingPermissions.values())if(a.eventId===this.activeRun.eventId){i=a;break}}if(!i)return this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"unknown_or_expired_approval_id","That approval request is no longer pending."),{handled:!0,kind:"permission"};const n=e.action_type==="exec_approve"||e.action_type==="permission_approve",o=n?"once":"reject";return clearTimeout(i.timer),this.pendingPermissions.delete(i.permissionId),this.transport.respondPermission(i.ocSessionId,i.permissionId,o,i.cwd).then(()=>{r.info("opencode-adapter",`Permission ${o}: ${i.permissionId}`),this.resetIdleTimer()}).catch(a=>{r.warn("opencode-adapter",`Permission response failed: ${a}`),this.resetIdleTimer()}),this.callbacks.sendLocalActionResult(e.action_id,"ok",{approval_id:i.permissionId,decision:n?"approve":"reject"}),{handled:!0,kind:"permission"}}onQuestionTimeout(e){const s=this.pendingQuestions.get(e);s&&(this.pendingQuestions.delete(e),r.error("opencode-adapter",`Question answer timeout: ${e}`),this.transport.rejectQuestion(e,s.cwd).catch(()=>{}),this.activeRun&&this.activeRun.eventId===s.eventId&&(this.flushTextBuffer(),this.finishRun("failed","question answer timeout")))}handleQuestionAsked(e){if(!this.activeRun){this.transport.rejectQuestion(e.id).catch(n=>{r.warn("opencode-adapter",`Orphan question reject failed: ${n}`)});return}const{eventId:s,sessionId:t}=this.activeRun;this.stopIdleTimer();const i=setTimeout(()=>this.onQuestionTimeout(e.id),S);if(this.pendingQuestions.set(e.id,{requestId:e.id,eventId:s,cwd:this.activeRun.cwd,questions:e.questions,timer:i}),this.callbacks.sendAgentQuestionCard)this.callbacks.sendAgentQuestionCard(s,t,{request_id:e.id,mode:"form",questions:e.questions.map(n=>({header:n.header,prompt:n.question,...n.options.length>0?{options:n.options.map(o=>o.label)}:{},...n.multiple!==void 0?{multi_select:n.multiple}:{}})),expires_at:Date.now()+S});else{const n=e.questions.map(o=>o.question).join(`
|
|
4
|
-
`);this.callbacks.sendRunError(s,t,`Agent asks: ${n}`)}}handleQuestionReplyAction(e){const s=e.params??{},t=this.deliverQuestionReply(s);return t.delivered?this.callbacks.sendLocalActionResult(e.action_id,"ok",t.result):this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,t.errorCode,t.errorMsg),{handled:!0,kind:"question_reply"}}deliverQuestionReply(e){const s=String(e.request_id??""),t=s?this.pendingQuestions.get(s):void 0;if(!t)return{delivered:!1,errorCode:"interaction_request_not_pending",errorMsg:"The question is no longer pending; the reply was not delivered."};const i=e.resolution??{},n=String(i.type??""),o=(d,h)=>(clearTimeout(t.timer),this.pendingQuestions.delete(t.requestId),d().then(()=>this.resetIdleTimer()).catch(c=>{r.warn("opencode-adapter",`Question response failed: ${c}`),this.resetIdleTimer()}),{delivered:!0,result:h});if(n==="action"){const d=String(i.value??"");if(d==="cancel"||d==="decline")return o(()=>this.transport.rejectQuestion(t.requestId,t.cwd),{request_id:s,resolution:"cancel"})}let a=null;if(Array.isArray(e.answers))a=e.answers.map(d=>Array.isArray(d)?d.map(String):[String(d)]);else if(n==="text"){const d=String(i.value??"");d&&(a=t.questions.map((h,c)=>c===0?[d]:[]))}else if(n==="map"){const d=Array.isArray(i.entries)?i.entries:[];a=t.questions.map((h,c)=>{const
|
|
1
|
+
import{EventEmitter as _}from"node:events";import{stat as E}from"node:fs/promises";import{existsSync as M,mkdirSync as $,readFileSync as O,writeFileSync as F}from"node:fs";import{join as S,resolve as D,dirname as B}from"node:path";import{homedir as L}from"node:os";import{fileURLToPath as N}from"node:url";import{resolveCommandPath as j,spawnCommand as Q,killProcessGroup as y,hasChildProcesses as U}from"../../core/runtime/spawn.js";import{InternalApiServer as q}from"../../core/mcp/internal-api-server.js";import{resolveMcpServerName as H,pruneLegacyMcpServerEntries as J}from"../../core/mcp/server-name.js";import{InboundMessageIdInjector as W}from"../shared/inbound-message-id-injector.js";import{IdentityInjector as z}from"../shared/identity-injector.js";import{syncDefaultSkillsToDir as G}from"../../default-skills/index.js";import{buildSimpleProbeReport as V}from"../shared/probe-util.js";import{compactToolInputForWire as X}from"../shared/tool-wire-payload.js";import{normalizeOCProviderModels as Y}from"./opencode-types.js";import{buildOpencodeConfigContent as K}from"./opencode-config.js";import{formatOpenCodeErrorMessage as k}from"./format-error.js";import{OpenCodeTransport as Z}from"./opencode-transport.js";import{log as r}from"../../core/log/index.js";import{splitTextForAibotProtocol as A}from"../../core/protocol/index.js";class ee extends _{adapterSessionId;constructor(e){super(),this.adapterSessionId=e}emitError(e){if(this.listenerCount("error")===0){r.warn("opencode-adapter",`Prompt handle error (no listeners): ${e.message}`);return}this.emit("error",e)}async cancel(){}}const te=200,se=2e3,ie=12e4,ne=90*1e3,x=1800*1e3,b=3e4,R=600*1e3,oe="claude_interaction_reply",re="set_mode",ae="set_model",de="127.0.0.1",ce=0,le=1e3,m={fullAuto:"full_auto",approval:"approval"},P=[{id:m.fullAuto,displayName:"Full Auto"},{id:m.approval,displayName:"Approval"}],he=[{permission:"*",pattern:"*",action:"allow"}],pe=[{permission:"doom_loop",pattern:"*",action:"ask"},{permission:"external_directory",pattern:"*",action:"ask"},{permission:"question",pattern:"*",action:"deny"},{permission:"plan_enter",pattern:"*",action:"deny"},{permission:"plan_exit",pattern:"*",action:"deny"},{permission:"repo_clone",pattern:"*",action:"deny"},{permission:"repo_overview",pattern:"*",action:"deny"},{permission:"read",pattern:"*.env",action:"ask"},{permission:"read",pattern:"*.env.*",action:"ask"},{permission:"read",pattern:"*.env.example",action:"allow"}];function w(v){const e=String(v??"").trim().toLowerCase();if(e==="full_auto"||e==="fullauto")return m.fullAuto;if(e==="approval"||e==="ask"||e==="autoedit"||e==="auto_edit")return m.approval}function C(v){try{return process.kill(v,0),!0}catch(e){return e.code==="EPERM"}}class $e extends _{type="opencode";config;callbacks;options;identity;inboundMessageIds=new W;process=null;transport=new Z;alive=!1;stopped=!1;internalApi=null;sessions=new Map;sessionModes=new Map;sessionModels=new Map;availableModels=[];modelContextLimits=new Map;contextTokensBySession=new Map;activeRun=null;completedEvents=new Set;clientMsgSeq=0;auditCaptures=new Map;completedAuditBoundaries=new Map;idleTimer=null;lastSseAt=0;livenessExtendStartAt;toolCallsInFlight=new Set;toolCardsSent=new Set;messageRoles=new Map;pendingPartChunks=new Map;pendingPermissions=new Map;pendingQuestions=new Map;permissionHandler=null;constructor(e,s,t){super(),this.config=e,this.callbacks=s,this.options=t??{},this.identity=new z("opencode-adapter",s.getAgentProfile)}onAgentProfileChanged(){this.identity.onProfileChanged()}async start(){await this.startInternalApiAndInjectMcp();const e=this.options.hostname??de,s=this.options.port??ce,t=await this.spawnAndWait(e,s),i=this.resolveCwd();await this.transport.connect(t,i),this.transport.on("event",n=>this.handleSseEvent(n));try{await this.refreshModels()}catch(n){r.warn("opencode-adapter",`Failed to fetch providers for toolbar models: ${n instanceof Error?n.message:String(n)}`)}r.info("opencode-adapter",`Ready (pid=${this.process?.pid}, url=${t})`)}async refreshModels(e){const s=await this.transport.listProviders(e),t=[],i=new Map;for(const n of s)for(const o of Y(n.models)){t.push({id:`${n.id}/${o.id}`,displayName:o.name||o.id});const a=o.limit?.context;typeof a=="number"&&a>0&&i.set(`${n.id}/${o.id}`,a)}t.sort((n,o)=>{const a=n.displayName.toLowerCase().localeCompare(o.displayName.toLowerCase());return a!==0?a:n.id.localeCompare(o.id)}),this.availableModels=t,this.modelContextLimits=i,r.info("opencode-adapter",`Fetched ${t.length} models from ${s.length} providers (directory=${e??"current"})`)}resolveContextLimit(e,s){const t=this.modelContextLimits.get(`${e}/${s}`);if(t)return t;let i;for(const[n,o]of this.modelContextLimits)n.startsWith(`${e}/`)&&(i=i===void 0?o:Math.max(i,o));return i}buildContextWindowMeta(e,s,t,i){if(!i||i<=0)return;const n=this.resolveContextLimit(s,t);if(!n)return;const o=Math.min(100,i/n*100);return{context_window:{used:i,size:n,usedPercentage:o,remainingPercentage:100-o}}}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{y(e,"SIGTERM")}catch{}const s=setTimeout(()=>{try{y(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();await this.transport.useDirectory(s);const t=await this.transport.createSession({title:`grix-${Date.now()}`},s);return this.sessions.set(t.id,{ocSessionId:t.id,cwd:s}),r.info("opencode-adapter",`Created OC session ${t.id} for cwd=${s}`),await this.applySessionPermission(t.id,t.id,s),t.id}async resumeSession(e,s){const t=this.sessions.get(e);if(t?.ocSessionId)try{await this.transport.useDirectory(t.cwd),await this.transport.getSession(t.ocSessionId,t.cwd),await this.applySessionPermission(e,t.ocSessionId,t.cwd)}catch{r.warn("opencode-adapter",`OC session ${t.ocSessionId} gone, will create new on next prompt`),t.ocSessionId=""}}async destroySession(e){const s=this.sessions.get(e),t=s?s.ocSessionId:e;if(t)try{await this.transport.deleteSession(t,s?.cwd)}catch{}this.sessions.delete(e),this.contextTokensBySession.delete(e),this.identity.forgetSession(e),this.inboundMessageIds.forgetSession(e)}sendPrompt(e){const s=new ee(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,this.activeRun.cwd)}catch{}}deliverInboundEvent(e){const{event_id:s,session_id:t,content:i}=e;if(this.completedEvents.has(s)){r.info("opencode-adapter",`Dropping duplicate event ${s}`),this.callbacks.sendEventAck(s,t),this.callbacks.sendEventResult(s,"responded");return}if(!this.alive){r.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&&(r.info("opencode-adapter",`steer: ${this.activeRun.eventId} -> ${s}`),this.flushTextBuffer(),this.callbacks.sendEventResult(this.activeRun.eventId,"canceled","steered to new event"),this.clearRun()),r.info("opencode-adapter",`prompt: event=${s} session=${t}`),this.callbacks.sendEventAck(s,t),this.startRun(s,t,e.audit?.enabled===!0),this.startComposing(t),this.resetIdleTimer();const n=this.buildPromptTextFromEvent(e);this.sendOcPrompt(t,n).catch(o=>{r.error("opencode-adapter",`prompt_async failed: ${o}`),this.finishRun("failed",String(o))})}deliverStopEvent(e,s){this.activeRun&&this.activeRun.eventId===e&&(r.info("opencode-adapter",`stop: event=${e}`),this.rejectAllPendingInteractions(),this.transport.abortSession(this.activeRun.ocSessionId,this.activeRun.cwd).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]:[]}takeAuditBoundary(e){const s=this.completedAuditBoundaries.get(e);return s&&this.completedAuditBoundaries.delete(e),s}clearActiveEventForShutdown(){this.stopIdleTimer(),this.stopTextFlush(),this.rejectAllPendingInteractions(),this.activeRun&&this.snapshotAuditBoundary(this.activeRun),this.activeRun=null}getMcpConfig(){if(!this.internalApi)return null;const e=D(N(import.meta.url),"../../../mcp/acp-mcp-server.js");return{name:H(this.callbacks.getAgentId?.()),command:process.execPath,args:[e,"--api-url",this.internalApi.url]}}async hasBackgroundWork(){const e=this.process?.pid;return e?U(e,[e]):!1}async probe(e){const s=this.getStatus();return V(this.config.command||"opencode",{alive:s.alive,busy:s.busy,started:!!this.process},e)}async startInternalApiAndInjectMcp(){try{this.internalApi=new q,this.internalApi.setInvokeHandler(async(f,d,h,c)=>this.callbacks.agentInvoke(f,d,c)),await this.internalApi.start(0),r.info("opencode-adapter",`Internal API started at ${this.internalApi.url}`);const e=this.getMcpConfig(),s=process.env.XDG_CONFIG_HOME||S(L(),".config"),t=S(s,"opencode","opencode.json");$(B(t),{recursive:!0});let i={};try{M(t)&&(i=JSON.parse(O(t,"utf8")))}catch{}const n=i.mcp&&typeof i.mcp=="object"?i.mcp:{};J(n,this.internalApi.url),n[e.name]={type:"local",command:[e.command,...e.args??[]],enabled:!0},i.mcp=n,F(t,`${JSON.stringify(i,null,2)}
|
|
2
|
+
`,"utf8"),r.info("opencode-adapter",`MCP config injected into ${t}`);const o=S(s,"opencode","skills"),a=G(o);a.length>0&&r.info("opencode-adapter",`Synced connector skills to ${o}: [${a.join(", ")}]`)}catch(e){r.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;if(s==="exec_approve"||s==="exec_reject"||s==="permission_approve"||s==="permission_reject")return this.handlePermissionAction(e);if(s===re)return this.handleSetModeAction(e);if(s===ae)return this.handleSetModelAction(e);if(s===oe){const t=e.params??{};if(String(t.kind??"")==="permission"){const i=t.resolution??{},n=String(i.value??"");return this.handlePermissionAction({...e,action_type:n==="allow"?"exec_approve":"exec_reject",params:{...t,approval_command_id:t.request_id}})}return this.handleQuestionReplyAction(e)}return{handled:!1,kind:""}}handleQuestionReplyEvent(e){let s;if(e.action)s={type:"action",value:e.action};else if(e.response?.type==="single")s={type:"text",value:e.response.value};else if(e.response?.type==="map")s={type:"map",entries:e.response.entries};else return{delivered:!1,errorCode:"resolution_invalid",errorMsg:"Question reply response is invalid."};return this.deliverQuestionReply({request_id:e.request_id,resolution:s})}async bindSession(e,s){if(r.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}try{await this.refreshModels(s)}catch(t){r.warn("opencode-adapter",`Failed to refresh models on bind: ${t instanceof Error?t.message:String(t)}`)}this.callbacks.sendUpdateBindingCard(e,"ready",s,this.buildToolbarMeta(e))}resolveModeId(e){if(e){const t=this.sessionModes.get(e);if(t)return t}const s=w(this.options.permissionPolicy);if(s)return s;if(e){const t=w(this.options.bindingStore?.get(e)?.modeId);if(t)return t}return m.fullAuto}buildModeToolbarMeta(e){const s=this.resolveModeId(e);return{mode_id:s,currentModeId:s,available_modes:P.map(t=>({...t}))}}async applySessionPermission(e,s,t){const i=this.resolveModeId(e),n=i===m.fullAuto?he:pe;try{await this.transport.updateSessionPermission(s,n,t),r.info("opencode-adapter",`Applied session permission ruleset (${i}) to ${s}`)}catch(o){r.warn("opencode-adapter",`Failed to apply session permission ruleset: ${o instanceof Error?o.message:String(o)}`)}}async handleSetModeAction(e){const s=e.params??{},t=String(s.session_id??this.options.aibotSessionId??"").trim(),i=w(s.mode_id);if(!i||!t)return this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"invalid_params","mode_id must be full_auto or approval and session_id is required"),{handled:!0,kind:"set_mode"};this.sessionModes.set(t,i);const n=this.sessions.get(t);n?.ocSessionId&&await this.applySessionPermission(t,n.ocSessionId,n.cwd);const o=n?.cwd??this.getSessionCwd(t);return this.callbacks.sendUpdateBindingCard(t,"ready",o,this.buildToolbarMeta(t)),this.callbacks.sendLocalActionResult(e.action_id,"ok",{outcome:"mode_set",session_context:{mode_id:i,modeId:i},mode_id:i,available_modes:P.map(a=>({...a}))}),{handled:!0,kind:"set_mode"}}resolveModelId(e){if(e){const t=this.sessionModels.get(e);if(t)return t}const s=String(this.options.model??"").trim();if(s)return s;if(e){const t=String(this.options.bindingStore?.get(e)?.modelId??"").trim();if(t)return t}return""}buildModelToolbarMeta(e){const s=this.resolveModelId(e);return{model_id:s,currentModelId:s,available_models:this.availableModels.map(t=>({...t}))}}buildToolbarMeta(e){return{...this.buildModelToolbarMeta(e),...this.buildModeToolbarMeta(e)}}handleSetModelAction(e){const s=e.params??{},t=String(s.session_id??this.options.aibotSessionId??"").trim(),i=String(s.model_id??"").trim();if(!i||!t)return this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"invalid_params","model_id and session_id are required"),{handled:!0,kind:"set_model"};this.sessionModels.set(t,i);const n=this.sessions.get(t)?.cwd??this.getSessionCwd(t);return this.callbacks.sendUpdateBindingCard(t,"ready",n,this.buildToolbarMeta(t)),this.callbacks.sendLocalActionResult(e.action_id,"ok",{outcome:"model_set",session_context:{model_id:i,modelId:i},model_id:i,available_models:this.availableModels.map(o=>({...o}))}),{handled:!0,kind:"set_model"}}async spawnAndWait(e,s){const t=this.resolveCwd();try{if(!(await E(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,n)=>{const o=this.config.command||"opencode",f=[...this.config.args??["serve"],`--hostname=${e}`,`--port=${s}`],d={...process.env,...this.config.env},h=K({model:this.options.model,permissionPolicy:this.options.permissionPolicy,provider:this.options.provider});h&&(d.OPENCODE_CONFIG_CONTENT=JSON.stringify(h));const c=j(o,typeof d.PATH=="string"?d.PATH:void 0);r.info("opencode-adapter",`Spawning: ${c} ${f.join(" ")} (cwd=${t})`),this.process=Q(c,f,{env:d,cwd:t}).process;let u="",p=!1;const I=setTimeout(()=>{p||(p=!0,n(new Error(`opencode serve did not start after ${b/1e3}s`)))},b);this.process.stdout?.on("data",l=>{if(u+=l.toString(),!p)for(const g of u.split(`
|
|
3
|
+
`)){const T=g.match(/opencode server listening on (https?:\/\/[^\s]+)/);if(T){clearTimeout(I),p=!0,this.alive=!0,i(T[1]);return}}}),this.process.stderr?.on("data",l=>{const g=l.toString().trim();g&&r.info("opencode-adapter",`[stderr] ${g}`)}),this.process.on("error",l=>{r.error("opencode-adapter",`Spawn error: ${l.message}`),clearTimeout(I),this.alive=!1,this.transport.disconnect(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed",`Spawn error: ${l.message}`),this.clearRun()),p?this.stopped||this.emit("exit",1):(p=!0,n(l))}),this.process.on("exit",l=>{r.info("opencode-adapter",`Process exited (code=${l})`),clearTimeout(I),this.alive=!1,this.transport.disconnect(),this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed",`Process exited (code=${l})`),this.clearRun()),p?this.stopped||this.emit("exit",l??1):(p=!0,n(new Error(`opencode serve exited with code ${l}`)))})})}async ensureOcSession(e){const s=this.sessions.get(e),t=this.getSessionCwd(e);if(await this.transport.useDirectory(t),s?.ocSessionId)try{return await this.transport.getSession(s.ocSessionId,t),s.ocSessionId}catch{r.warn("opencode-adapter",`OC session ${s.ocSessionId} gone, creating new`),s.ocSessionId=""}const i=this.options.bindingStore?.getAcpSessionId(e);if(i)try{return await this.transport.getSession(i,t),this.sessions.set(e,{ocSessionId:i,cwd:t}),r.info("opencode-adapter",`Resumed OC session ${i} for aibot=${e}`),await this.applySessionPermission(e,i,t),i}catch{r.warn("opencode-adapter",`Persisted OC session ${i} gone, creating new`)}const n=await this.transport.createSession({title:`grix-${e.slice(-8)}`},t);return this.sessions.set(e,{ocSessionId:n.id,cwd:t}),this.options.bindingStore?.setAcpSessionId(e,n.id),r.info("opencode-adapter",`Created OC session ${n.id} for aibot=${e}`),await this.applySessionPermission(e,n.id,t),n.id}async sendOcPrompt(e,s){const t=this.getSessionCwd(e),i=await this.ensureOcSession(e);this.activeRun&&(this.activeRun.ocSessionId=i,this.activeRun.cwd=t);const n=this.resolveModelId(e),o=n.indexOf("/"),a=o>0&&o<n.length-1?{providerID:n.slice(0,o),modelID:n.slice(o+1)}:void 0;await this.transport.sendPromptAsync(i,{parts:[{type:"text",text:s}],...a?{model:a}:{}},t)}handleSseEvent(e){if(!this.stopped){if(this.updateToolInFlight(e),e.type!=="server.heartbeat"&&(this.lastSseAt=Date.now(),this.resetIdleTimer()),this.activeRun){const s=this.auditCaptures.get(this.activeRun.eventId);s&&s.push({sequence:s.length,observedAt:new Date().toISOString(),event:structuredClone(e)})}switch(e.type){case"message.part.updated":{if(!this.activeRun)break;const s=e.part,t=e.delta;if(s.type==="text"){const i=t||s.text;i&&this.acceptAssistantPartText(s.messageID,"text",i)}else s.type==="reasoning"?t&&this.acceptAssistantPartText(s.messageID,"reasoning",t):s.type==="tool"&&this.handleToolPartUpdate(s,t);break}case"message.updated":{if(!this.activeRun)break;const s=e.info;if(this.rememberMessageRole(s),s.role==="assistant"){const t=s;typeof t.tokens?.input=="number"&&this.contextTokensBySession.set(this.activeRun.sessionId,t.tokens.input);const i=this.buildContextWindowMeta(this.activeRun.sessionId,t.providerID,t.modelID,t.tokens?.input);this.callbacks.sendUpdateBindingCard(this.activeRun.sessionId,"composing",this.activeRun.cwd,{...this.buildToolbarMeta(this.activeRun.sessionId),model_provider:t.providerID,model_id:t.modelID,...i??{}}),t.error&&r.warn("opencode-adapter",`Message error: ${JSON.stringify(t.error)}`);const n=this.activeRun.ocSessionId;n&&t.sessionID===n&&(t.error?(this.flushTextBuffer(),this.finishRun("failed",k(t.error,t.error.type||"unknown error"))):t.time?.completed&&t.finish!=="tool-calls"&&(this.flushTextBuffer(),this.finishRun("responded")))}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=k(e.error);r.error("opencode-adapter",`Session error: ${s}`),this.flushTextBuffer(),this.finishRun("failed",s);break}case"permission.updated":{this.handlePermission(e.permission);break}case"permission.asked":{this.handlePermissionAsked(e);break}case"question.asked":{this.handleQuestionAsked(e);break}case"permission.replied":case"question.replied":case"question.rejected":r.debug("opencode-adapter",`Interaction settled: ${e.type}`);break;case"session.created":case"session.updated":case"session.deleted":case"session.compacted":case"session.diff":case"file.edited":case"server.connected":case"server.heartbeat":break;case"server.instance.disposed":{r.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:r.debug("opencode-adapter",`Unhandled SSE event type: ${e.type}`);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)}rememberMessageRole(e){const s=typeof e.id=="string"?e.id.trim():"";s&&(e.role!=="user"&&e.role!=="assistant"||(this.messageRoles.set(s,e.role),this.flushOrDropPendingParts(s,e.role)))}acceptAssistantPartText(e,s,t){if(!this.activeRun||!t)return;const i=typeof e=="string"?e.trim():"";if(!i)return;const n=this.messageRoles.get(i);if(n==="user")return;if(n==="assistant"){this.emitAssistantPartChunk(s,t);return}const o=this.pendingPartChunks.get(i)??[];o.push({kind:s,value:t}),this.pendingPartChunks.set(i,o)}flushOrDropPendingParts(e,s){const t=this.pendingPartChunks.get(e);if(this.pendingPartChunks.delete(e),!(!t||t.length===0)&&s==="assistant")for(const i of t)this.emitAssistantPartChunk(i.kind,i.value)}emitAssistantPartChunk(e,s){if(!(!this.activeRun||!s)){if(e==="reasoning"){this.callbacks.sendThinking(this.activeRun.eventId,this.activeRun.sessionId,s);return}this.appendText(s)}}clearMessageRoleState(){this.messageRoles.clear(),this.pendingPartChunks.clear()}handleToolPartUpdate(e,s){if(!this.activeRun)return;const t=e.state;switch(t.status){case"pending":case"running":{this.sendToolUseOnce(e,t.input);break}case"completed":{this.sendToolUseOnce(e,t.input);break}case"error":{this.sendToolUseOnce(e,t.input),this.callbacks.sendToolResult(this.activeRun.eventId,this.activeRun.sessionId,e.tool,"(failed)");break}}}sendToolUseOnce(e,s){!this.activeRun||this.toolCardsSent.has(e.callID)||(this.flushTextBuffer(),this.toolCardsSent.add(e.callID),this.callbacks.sendToolUse(this.activeRun.eventId,this.activeRun.sessionId,e.tool,X(s??{})))}onPermissionTimeout(e){const s=this.pendingPermissions.get(e);s&&(this.pendingPermissions.delete(e),r.error("opencode-adapter",`Permission approval timeout: ${e}`),this.transport.respondPermission(s.ocSessionId,e,"reject",s.cwd).catch(()=>{}),this.activeRun&&this.activeRun.eventId===s.eventId&&(this.flushTextBuffer(),this.finishRun("failed","permission approval timeout")))}trackPendingPermission(e){if(!this.activeRun)return;this.stopIdleTimer();const s=setTimeout(()=>this.onPermissionTimeout(e),R);this.pendingPermissions.set(e,{permissionId:e,eventId:this.activeRun.eventId,ocSessionId:this.activeRun.ocSessionId,cwd:this.activeRun.cwd,timer:s})}handlePermissionAsked(e){if(!this.activeRun){this.transport.respondPermission(e.sessionID,e.id,"reject").catch(o=>{r.warn("opencode-adapter",`Orphan permission reject failed: ${o}`)});return}const{eventId:s,sessionId:t,ocSessionId:i}=this.activeRun;if(this.resolveModeId(t)===m.fullAuto){this.transport.respondPermission(i,e.id,"always",this.activeRun.cwd).catch(o=>{r.warn("opencode-adapter",`Auto-approve failed: ${o}`)});return}this.trackPendingPermission(e.id);const n=e.patterns.length>0?`${e.permission}: ${e.patterns.join(", ")}`:e.permission;this.callbacks.sendPermissionCard?this.callbacks.sendPermissionCard({eventId:s,sessionId:t,approvalId:e.id,toolName:e.permission,toolTitle:n,toolInput:JSON.stringify(e.metadata??{})}):this.callbacks.sendToolUse(s,t,e.permission,JSON.stringify(e.metadata??{}))}handlePermission(e){if(!this.activeRun)return;const{eventId:s,sessionId:t,ocSessionId:i}=this.activeRun;if(this.resolveModeId(t)===m.fullAuto){this.transport.respondPermission(i,e.id,"always",this.activeRun.cwd).catch(n=>{r.warn("opencode-adapter",`Auto-approve failed: ${n}`)});return}if(this.trackPendingPermission(e.id),this.callbacks.sendPermissionCard)this.callbacks.sendPermissionCard({eventId:s,sessionId:t,approvalId:e.id,toolName:e.type,toolTitle:e.title||e.type,toolInput:JSON.stringify(e.metadata??{})});else{const n=JSON.stringify(e.metadata??{});this.callbacks.sendToolUse(s,t,e.type,n)}}handlePermissionAction(e){if(!this.activeRun)return{handled:!1,kind:""};const s=e.params??{},t=String(s.approval_id??s.approval_command_id??s.approvalId??s.tool_call_id??"");let i=t?this.pendingPermissions.get(t):void 0;if(!i){for(const a of this.pendingPermissions.values())if(a.eventId===this.activeRun.eventId){i=a;break}}if(!i)return this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"unknown_or_expired_approval_id","That approval request is no longer pending."),{handled:!0,kind:"permission"};const n=e.action_type==="exec_approve"||e.action_type==="permission_approve",o=n?"once":"reject";return clearTimeout(i.timer),this.pendingPermissions.delete(i.permissionId),this.transport.respondPermission(i.ocSessionId,i.permissionId,o,i.cwd).then(()=>{r.info("opencode-adapter",`Permission ${o}: ${i.permissionId}`),this.resetIdleTimer()}).catch(a=>{r.warn("opencode-adapter",`Permission response failed: ${a}`),this.resetIdleTimer()}),this.callbacks.sendLocalActionResult(e.action_id,"ok",{approval_id:i.permissionId,decision:n?"approve":"reject"}),{handled:!0,kind:"permission"}}onQuestionTimeout(e){const s=this.pendingQuestions.get(e);s&&(this.pendingQuestions.delete(e),r.error("opencode-adapter",`Question answer timeout: ${e}`),this.transport.rejectQuestion(e,s.cwd).catch(()=>{}),this.activeRun&&this.activeRun.eventId===s.eventId&&(this.flushTextBuffer(),this.finishRun("failed","question answer timeout")))}handleQuestionAsked(e){if(!this.activeRun){this.transport.rejectQuestion(e.id).catch(n=>{r.warn("opencode-adapter",`Orphan question reject failed: ${n}`)});return}const{eventId:s,sessionId:t}=this.activeRun;this.stopIdleTimer();const i=setTimeout(()=>this.onQuestionTimeout(e.id),R);if(this.pendingQuestions.set(e.id,{requestId:e.id,eventId:s,cwd:this.activeRun.cwd,questions:e.questions,timer:i}),this.callbacks.sendAgentQuestionCard)this.callbacks.sendAgentQuestionCard(s,t,{request_id:e.id,mode:"form",questions:e.questions.map(n=>({header:n.header,prompt:n.question,...n.options.length>0?{options:n.options.map(o=>o.label)}:{},...n.multiple!==void 0?{multi_select:n.multiple}:{}})),expires_at:Date.now()+R});else{const n=e.questions.map(o=>o.question).join(`
|
|
4
|
+
`);this.callbacks.sendRunError(s,t,`Agent asks: ${n}`)}}handleQuestionReplyAction(e){const s=e.params??{},t=this.deliverQuestionReply(s);return t.delivered?this.callbacks.sendLocalActionResult(e.action_id,"ok",t.result):this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,t.errorCode,t.errorMsg),{handled:!0,kind:"question_reply"}}deliverQuestionReply(e){const s=String(e.request_id??""),t=s?this.pendingQuestions.get(s):void 0;if(!t)return{delivered:!1,errorCode:"interaction_request_not_pending",errorMsg:"The question is no longer pending; the reply was not delivered."};const i=e.resolution??{},n=String(i.type??""),o=(d,h)=>(clearTimeout(t.timer),this.pendingQuestions.delete(t.requestId),d().then(()=>this.resetIdleTimer()).catch(c=>{r.warn("opencode-adapter",`Question response failed: ${c}`),this.resetIdleTimer()}),{delivered:!0,result:h});if(n==="action"){const d=String(i.value??"");if(d==="cancel"||d==="decline")return o(()=>this.transport.rejectQuestion(t.requestId,t.cwd),{request_id:s,resolution:"cancel"})}let a=null;if(Array.isArray(e.answers))a=e.answers.map(d=>Array.isArray(d)?d.map(String):[String(d)]);else if(n==="text"){const d=String(i.value??"");d&&(a=t.questions.map((h,c)=>c===0?[d]:[]))}else if(n==="map"){const d=Array.isArray(i.entries)?i.entries:[];a=t.questions.map((h,c)=>{const u=d.find(p=>p.key===h.header||p.key===String(c+1));return u&&u.value?[u.value]:[]}),a.every(h=>h.length===0)&&d.length>0&&(a=t.questions.map((h,c)=>{const u=d[c]?.value;return u?[String(u)]:[]}))}if(!a||a.every(d=>d.length===0))return o(()=>this.transport.rejectQuestion(t.requestId,t.cwd),{request_id:s,resolution:"cancel"});const f=a;return o(()=>this.transport.replyQuestion(t.requestId,f,t.cwd),{request_id:s,resolution:"answer"})}rejectAllPendingInteractions(){for(const e of this.pendingPermissions.values())clearTimeout(e.timer),this.transport.respondPermission(e.ocSessionId,e.permissionId,"reject",e.cwd).catch(()=>{});this.pendingPermissions.clear();for(const e of this.pendingQuestions.values())clearTimeout(e.timer),this.transport.rejectQuestion(e.requestId,e.cwd).catch(()=>{});this.pendingQuestions.clear()}hasPendingInteractionForRun(e){for(const s of this.pendingPermissions.values())if(s.eventId===e)return!0;for(const s of this.pendingQuestions.values())if(s.eventId===e)return!0;return!1}startRun(e,s,t=!1){this.activeRun={eventId:e,sessionId:s,ocSessionId:"",cwd:this.getSessionCwd(s),chunkSeq:0,clientMsgId:`oc_${++this.clientMsgSeq}_${Date.now()}`,textBuffer:"",flushTimer:null},this.toolCardsSent.clear(),t&&this.auditCaptures.set(e,[]),this.lastSseAt=Date.now(),this.livenessExtendStartAt=void 0}finishRun(e,s){const t=this.activeRun;if(!t)return;if(this.completedEvents.add(t.eventId),this.completedEvents.size>le){const a=[...this.completedEvents].slice(-500);this.completedEvents=new Set(a)}this.snapshotAuditBoundary(t),this.activeRun=null,this.rejectAllPendingInteractions(),this.toolCallsInFlight.clear(),this.toolCardsSent.clear(),this.livenessExtendStartAt=void 0,this.clearMessageRoleState(),this.stopComposing(),this.stopIdleTimer();const i=++t.chunkSeq,n=t.clientMsgId;e==="failed"&&s&&this.callbacks.sendRunError(t.eventId,t.sessionId,s);const o=()=>{this.callbacks.sendFinalStreamChunkReliable?this.callbacks.sendFinalStreamChunkReliable(t.eventId,t.sessionId,n).then(()=>{this.callbacks.sendEventResult(t.eventId,e,s)}).catch(a=>{const f=e==="responded"?"failed":e,d=e==="responded"?`final output delivery failed: ${a instanceof Error?a.message:String(a)}`:s;this.callbacks.sendEventResult(t.eventId,f,d)}):(this.callbacks.sendStreamChunk(t.eventId,t.sessionId,"",i,!0,n),this.callbacks.sendEventResult(t.eventId,e,s))};if(t.textBuffer){this.stopTextFlush();for(const a of A(t.textBuffer))t.chunkSeq++,this.callbacks.sendStreamChunk(t.eventId,t.sessionId,a,t.chunkSeq,!1,t.clientMsgId);t.textBuffer=""}o(),this.emit("eventDone",t.eventId)}clearRun(){this.activeRun?.flushTimer&&clearTimeout(this.activeRun.flushTimer);const e=this.activeRun?.eventId;this.activeRun&&this.snapshotAuditBoundary(this.activeRun),this.activeRun=null,this.rejectAllPendingInteractions(),this.toolCallsInFlight.clear(),this.toolCardsSent.clear(),this.livenessExtendStartAt=void 0,this.clearMessageRoleState(),this.stopIdleTimer(),e&&this.emit("eventDone",e)}snapshotAuditBoundary(e){const s=this.auditCaptures.get(e.eventId);s&&(this.completedAuditBoundaries.set(e.eventId,{adapterType:"opencode",providerSessionId:e.ocSessionId||void 0,openCodeEvidence:{events:s}}),this.auditCaptures.delete(e.eventId))}appendText(e){if(this.activeRun){if(this.activeRun.textBuffer+=e,this.activeRun.textBuffer.length>=se){this.flushTextBuffer();return}this.scheduleTextFlush()}}scheduleTextFlush(){!this.activeRun||this.activeRun.flushTimer||(this.activeRun.flushTimer=setTimeout(()=>{this.activeRun&&(this.activeRun.flushTimer=null),this.flushTextBuffer()},te))}flushTextBuffer(){if(this.stopTextFlush(),!(!this.activeRun||!this.activeRun.textBuffer)){for(const e of A(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(){}shouldExtendByLiveness(){if(!this.activeRun)return!1;const e=this.process?.pid;if(!this.alive||!e||!C(e))return!1;const s=Date.now(),t=this.lastSseAt>0?s-this.lastSseAt:Number.POSITIVE_INFINITY;if(t<ne)return this.livenessExtendStartAt=s,r.info("opencode-adapter",`Liveness check: fresh SSE for ${this.activeRun.eventId} (sseAge=${t}ms, toolsInFlight=${this.toolCallsInFlight.size}), extending`),!0;const n=this.livenessExtendStartAt??s;return this.livenessExtendStartAt===void 0&&(this.livenessExtendStartAt=n),s-n>x?(r.warn("opencode-adapter",`Liveness extension budget exhausted for ${this.activeRun.eventId} (no progress for ${Math.round((s-n)/6e4)}min), allowing idle fail`),!1):(r.info("opencode-adapter",`Liveness check: turn in progress for ${this.activeRun.eventId} (toolsInFlight=${this.toolCallsInFlight.size}, sseAge=${Number.isFinite(t)?`${t}ms`:"n/a"}), extending`),!0)}resetIdleTimer(){this.stopIdleTimer(),!(this.stopped||!this.activeRun)&&(this.idleTimer=setTimeout(()=>{if(this.stopped||!this.activeRun)return;if(this.hasPendingInteractionForRun(this.activeRun.eventId)&&this.shouldExtendByLiveness()){r.info("opencode-adapter",`Idle timeout skipped: pending interaction for ${this.activeRun.eventId}, resetting timer`),this.resetIdleTimer();return}if(this.shouldExtendByLiveness()){this.resetIdleTimer();return}const e=this.process?.pid,t=!!(this.alive&&e&&C(e))?`liveness budget exhausted after ${x/6e4}min`:"process not alive";r.error("opencode-adapter",`Idle timeout (${t}, toolsInFlight=${this.toolCallsInFlight.size}) \u2014 emitting exit for respawn`),this.flushTextBuffer(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed",`idle timeout: ${t}`),this.clearRun()),this.emit("exit",-1)},ie))}stopIdleTimer(){this.idleTimer&&(clearTimeout(this.idleTimer),this.idleTimer=null)}resolveCwd(){const e=this.options.aibotSessionId?this.options.bindingStore?.get(this.options.aibotSessionId)?.cwd:void 0;if(e)return e;const s=(this.config.options??{}).cwd;return typeof s=="string"&&s?s:process.cwd()}getSessionCwd(e){return this.sessions.get(e)?.cwd??this.options.bindingStore?.get(e)?.cwd??this.resolveCwd()}buildPromptText(e){let s=e.text;return e.contextMessages&&e.contextMessages.length>0&&(s=e.contextMessages.map(i=>`[context] ${i.senderId}: ${i.content}`).join(`
|
|
5
5
|
`)+`
|
|
6
6
|
|
|
7
7
|
`+s),this.identity.injectOnce(e.adapterSessionId,s)}buildPromptTextFromEvent(e){let s=e.content||"";if(e.context_messages_json)try{const i=JSON.parse(e.context_messages_json);Array.isArray(i)&&i.length>0&&(s=i.map(o=>`[context] ${o.sender_id??"unknown"}: ${o.content}`).join(`
|
|
8
8
|
`)+`
|
|
9
9
|
|
|
10
|
-
`+s)}catch{}const t=this.identity.injectOnce(e.session_id,s);return this.inboundMessageIds.inject(e.session_id,t,e.msg_id)}}export{
|
|
10
|
+
`+s)}catch{}const t=this.identity.injectOnce(e.session_id,s);return this.inboundMessageIds.inject(e.session_id,t,e.msg_id)}}export{$e as OpenCodeAdapter};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import{EventEmitter as f}from"node:events";import{stat as
|
|
2
|
-
`);i("sqlite3",[s,
|
|
1
|
+
import{EventEmitter as f}from"node:events";import{stat as _}from"node:fs/promises";import{mkdirSync as E}from"node:fs";import{homedir as u}from"node:os";import{join as p,resolve as k}from"node:path";import{fileURLToPath as S}from"node:url";import{randomUUID as w}from"node:crypto";import{resolveCommandPath as x,spawnCommand as b,killProcessGroup as v,hasChildProcesses as y}from"../../core/runtime/spawn.js";import{InternalApiServer as $}from"../../core/mcp/internal-api-server.js";import{InboundMessageIdInjector as A}from"../shared/inbound-message-id-injector.js";import{syncDefaultSkillsToDir as C}from"../../default-skills/index.js";import{buildSimpleProbeReport as M}from"../shared/probe-util.js";import{resolveMcpServerName as P,LEGACY_MCP_SERVER_NAMES as L}from"../../core/mcp/server-name.js";import{OpenHumanTransport as D,readBearerToken as N}from"./openhuman-transport.js";import{IdentityInjector as F}from"../shared/identity-injector.js";import{log as n}from"../../core/log/index.js";import{splitTextForAibotProtocol as B}from"../../core/protocol/index.js";class O extends f{adapterSessionId;constructor(t){super(),this.adapterSessionId=t}emitError(t){if(this.listenerCount("error")===0){n.warn("openhuman-adapter",`Prompt handle error (no listeners): ${t.message}`);return}this.emit("error",t)}async cancel(){}}const U=200,j=2e3,q=12e4,m=1800*1e3,H=5e3,X=500,T=3e4,G=7788,W="127.0.0.1";class dt extends f{type="openhuman";config;callbacks;options;identity;inboundMessageIds=new A;process=null;transport=new D;alive=!1;stopped=!1;internalApi=null;clientId;activeRun=null;completedEvents=new Set;clientMsgSeq=0;idleTimer=null;lastProgressAt=0;constructor(t,e,s){super(),this.config=t,this.callbacks=e,this.options=s??{},this.clientId=`grix-connector-${Date.now()}`,this.identity=new F("openhuman-adapter",e.getAgentProfile)}async start(){await this.startInternalApiAndInjectMcp();const t=this.options.host??W,e=this.options.port??G;await this.spawnProcess(t,e),await this.waitForServerReady(t,e);const s=this.resolveWorkspaceDir(),i=await N(s),o=`http://${t}:${e}`;if(await this.transport.connect(o,i),this.transport.on("event",a=>this.handleSocketEvent(a)),this.options.sessionToken)try{await this.transport.storeSession({token:this.options.sessionToken}),n.info("openhuman-adapter","Session token stored")}catch(a){n.warn("openhuman-adapter",`Failed to store session token: ${a}`)}this.transport.socketId&&(this.clientId=this.transport.socketId),n.info("openhuman-adapter",`Ready (pid=${this.process?.pid}, clientId=${this.clientId})`)}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 t=this.process;try{v(t,"SIGTERM")}catch{}const e=setTimeout(()=>{try{v(t,"SIGKILL")}catch{}},5e3);t.on("exit",()=>clearTimeout(e)),this.process=null}}isAlive(){return this.alive}async createSession(t){return this.clientId}async resumeSession(t,e){}onAgentProfileChanged(){this.identity.onProfileChanged()}async destroySession(t){this.identity.forgetSession(t),this.inboundMessageIds.forgetSession(t)}sendPrompt(t){const e=new O(t.adapterSessionId),s=this.buildPromptText(t);return this.doWebChat(t.adapterSessionId,s).then(i=>{this.activeRun&&(this.activeRun.requestId=i.request_id)}).catch(i=>{e.emitError(i instanceof Error?i:new Error(String(i)))}),e}async cancel(t){if(this.activeRun)try{await this.transport.webCancel({client_id:this.clientId,thread_id:this.activeRun.threadId})}catch{}}deliverInboundEvent(t){const{event_id:e,session_id:s,content:i}=t;if(this.completedEvents.has(e)){n.info("openhuman-adapter",`Dropping duplicate event ${e}`),this.callbacks.sendEventAck(e,s),this.callbacks.sendEventResult(e,"responded");return}if(!this.alive){n.warn("openhuman-adapter",`Dropping event ${e}: process not alive`),this.callbacks.sendEventAck(e,s),this.callbacks.sendEventResult(e,"failed","Agent process not running");return}this.activeRun&&this.activeRun.eventId!==e&&(n.info("openhuman-adapter",`steer: ${this.activeRun.eventId} -> ${e}`),this.flushTextBuffer(),this.callbacks.sendEventResult(this.activeRun.eventId,"canceled","steered to new event"),this.clearRun());const o=this.buildPromptTextFromEvent(t);n.info("openhuman-adapter",`prompt: event=${e} session=${s}`),this.callbacks.sendEventAck(e,s),this.startRun(e,s,s),this.startComposing(s,e),this.resetIdleTimer(),this.doWebChat(s,o).then(a=>{this.activeRun&&this.activeRun.eventId===e&&(this.activeRun.requestId=a.request_id,a.accepted||(n.warn("openhuman-adapter",`web_chat not accepted: ${a.request_id}`),this.finishRun("failed","Chat request not accepted")))}).catch(a=>{n.error("openhuman-adapter",`web_chat failed: ${a}`),this.finishRun("failed",String(a))})}deliverStopEvent(t,e){this.activeRun&&this.activeRun.eventId===t&&(n.info("openhuman-adapter",`stop: event=${t}`),this.transport.webCancel({client_id:this.clientId,thread_id:this.activeRun.threadId}).catch(()=>{}),this.flushTextBuffer(),this.finishRun("canceled","stopped by user"))}setPermissionHandler(t){}async ping(t){return this.transport.healthCheck()}getStatus(){return{alive:this.alive,busy:this.activeRun!==null,sessions:this.activeRun?1:0}}getActiveEventIds(){return this.activeRun?[this.activeRun.eventId]:[]}clearActiveEventForShutdown(){this.stopIdleTimer(),this.stopTextFlush(),this.activeRun=null}getMcpConfig(){if(!this.internalApi)return null;const t=k(S(import.meta.url),"../../../mcp/acp-mcp-server.js");return{name:P(this.callbacks.getAgentId?.()),command:process.execPath,args:[t,"--api-url",this.internalApi.url]}}async hasBackgroundWork(){const t=this.process?.pid;return t?y(t,[t]):!1}async probe(t){const e=this.getStatus();return M(this.config.command||"openhuman-core",{alive:e.alive,busy:e.busy,started:!!this.process},t)}async startInternalApiAndInjectMcp(){try{this.internalApi=new $,this.internalApi.setInvokeHandler(async(d,I,J,g)=>this.callbacks.agentInvoke(d,I,g)),await this.internalApi.start(0),n.info("openhuman-adapter",`Internal API started at ${this.internalApi.url}`);const t=this.getMcpConfig(),e=p(u(),".openhuman","users","local","workspace","mcp_clients"),s=p(e,"mcp_clients.db");E(e,{recursive:!0});const{execFileSync:i}=await import("node:child_process"),o=w(),a=JSON.stringify(t.args),c=["CREATE TABLE IF NOT EXISTS mcp_servers (server_id TEXT PRIMARY KEY, qualified_name TEXT NOT NULL, display_name TEXT NOT NULL, description TEXT, icon_url TEXT, command_kind TEXT NOT NULL DEFAULT 'node', command TEXT NOT NULL, args_json TEXT NOT NULL DEFAULT '[]', env_keys_json TEXT NOT NULL DEFAULT '[]', config_json TEXT, installed_at INTEGER NOT NULL, last_connected_at INTEGER);",`DELETE FROM mcp_servers WHERE qualified_name IN (${[...L,t.name].map(d=>`'${d}'`).join(", ")});`,`INSERT INTO mcp_servers (server_id, qualified_name, display_name, description, command_kind, command, args_json, installed_at) VALUES ('${o}', '${t.name}', 'Grix Connector Tools', 'Grix platform query and management tools', 'node', '${t.command}', '${a.replace(/'/g,"''")}', ${Date.now()});`].join(`
|
|
2
|
+
`);i("sqlite3",[s,c],{timeout:1e4,stdio:"ignore"}),n.info("openhuman-adapter",`MCP server registered in SQLite: ${s}`);const l=p(u(),".openhuman","skills"),r=C(l);r.length>0&&n.info("openhuman-adapter",`Synced connector skills to ${l}: [${r.join(", ")}]`)}catch(t){n.warn("openhuman-adapter",`Failed to inject MCP tools (non-fatal): ${t instanceof Error?t.message:String(t)}`)}}async spawnProcess(t,e){const s=this.config.command||"openhuman-core",i=x(s,typeof process.env.PATH=="string"?process.env.PATH:void 0),o=this.config.args??[],h=o.includes("run")||o.includes("serve")?o:["run","--host",t,"--port",String(e),...o],c={...process.env,...this.config.env},l=this.resolveCwd();n.info("openhuman-adapter",`Spawning: ${i} ${h.join(" ")}`);try{if(!(await _(l)).isDirectory())throw new Error(`Bound path is not a directory: ${l}`)}catch(r){throw String(r?.code??"")==="ENOENT"?new Error(`Bound directory does not exist: ${l}. Please rebind with /grix open <valid-directory>.`):r}try{this.process=b(i,h,{env:c,cwd:l}).process}catch(r){throw n.error("openhuman-adapter",`Spawn threw: ${r}`),this.alive=!1,r}this.process.on("error",r=>{n.error("openhuman-adapter",`Spawn error: ${r.message}`),this.alive=!1,this.transport.disconnect(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed",`Spawn error: ${r.message}`),this.clearRun()),this.stopped||this.emit("exit",1)}),this.process.on("exit",r=>{n.info("openhuman-adapter",`Process exited (code=${r})`),this.alive=!1,this.transport.disconnect(),this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed",`Process exited (code=${r})`),this.clearRun()),this.stopped||this.emit("exit",r??1)}),this.process.stderr?.on("data",r=>{const d=r.toString().trim();d&&n.info("openhuman-adapter",`[stderr] ${d}`)}),this.alive=!0}async waitForServerReady(t,e){const s=`http://${t}:${e}/health`,i=Date.now()+T;for(;Date.now()<i;){try{if((await fetch(s,{signal:AbortSignal.timeout(2e3)})).ok)return}catch{}await new Promise(o=>setTimeout(o,X))}throw new Error(`openhuman-core did not become ready at ${t}:${e} after ${T/1e3}s`)}async doWebChat(t,e){return this.transport.webChat({client_id:this.clientId,thread_id:t,message:e})}handleSocketEvent(t){if(this.stopped||!this.activeRun||t.request_id&&this.activeRun.requestId&&t.request_id!==this.activeRun.requestId)return;this.resetIdleTimer();const e=t.event;switch(e){case"text_delta":{t.delta&&t.delta_kind==="text"&&this.appendText(t.delta);break}case"thinking_delta":{t.delta&&t.delta_kind==="thinking"&&this.activeRun&&this.callbacks.sendThinking(this.activeRun.eventId,this.activeRun.sessionId,t.delta);break}case"tool_call":{if(this.flushTextBuffer(),this.activeRun&&t.tool_name){const s=typeof t.args=="string"?t.args:JSON.stringify(t.args??{});this.callbacks.sendToolUse(this.activeRun.eventId,this.activeRun.sessionId,t.tool_name,s)}break}case"tool_result":{if(this.activeRun&&t.tool_name){const s=t.output??"";this.callbacks.sendToolResult(this.activeRun.eventId,this.activeRun.sessionId,t.tool_name,s)}break}case"chat_done":{n.info("openhuman-adapter",`chat_done request=${t.request_id}`),this.activeRun&&this.activeRun.textBuffer.length===0&&t.full_response&&(this.activeRun.textBuffer=t.full_response),this.flushTextBuffer(),this.finishRun("responded");break}case"chat_error":{if(n.error("openhuman-adapter",`chat_error: ${t.error_type} ${t.message}`),this.flushTextBuffer(),this.activeRun){const s=t.message??t.error_type??"unknown error";this.callbacks.sendRunError(this.activeRun.eventId,this.activeRun.sessionId,s)}this.finishRun("failed",t.message);break}case"chat_segment":case"inference_start":case"iteration_start":case"chat_accepted":break;default:{n.debug("openhuman-adapter",`Unhandled event: ${e}`);break}}}startRun(t,e,s){this.activeRun={eventId:t,sessionId:e,requestId:"",threadId:s,chunkSeq:0,clientMsgId:`oh_${++this.clientMsgSeq}_${Date.now()}`,textBuffer:"",flushTimer:null}}finishRun(t,e){const s=this.activeRun;if(!s)return;if(this.completedEvents.add(s.eventId),this.completedEvents.size>1e3){const a=this.completedEvents.values();for(let c=0;c<500;c++)a.next();const h=[...this.completedEvents].slice(-500);this.completedEvents=new Set(h)}this.activeRun=null,this.emit("eventDone",s.eventId),this.stopComposing(),this.stopIdleTimer();const i=++s.chunkSeq,o=s.clientMsgId;t==="failed"&&e&&this.callbacks.sendRunError(s.eventId,s.sessionId,e),this.callbacks.sendFinalStreamChunkReliable?this.callbacks.sendFinalStreamChunkReliable(s.eventId,s.sessionId,i,o).then(()=>{this.callbacks.sendEventResult(s.eventId,t,e)}).catch(a=>{const h=t==="responded"?"failed":t,c=t==="responded"?`final output delivery failed: ${a instanceof Error?a.message:String(a)}`:e;this.callbacks.sendEventResult(s.eventId,h,c)}):(this.callbacks.sendStreamChunk(s.eventId,s.sessionId,"",i,!0,o),this.callbacks.sendEventResult(s.eventId,t,e))}clearRun(){this.activeRun?.flushTimer&&clearTimeout(this.activeRun.flushTimer);const t=this.activeRun?.eventId;this.activeRun=null,this.lastProgressAt=0,this.stopIdleTimer(),t&&this.emit("eventDone",t)}appendText(t){if(this.activeRun){if(this.activeRun.textBuffer+=t,this.activeRun.textBuffer.length>=j){this.flushTextBuffer();return}this.scheduleTextFlush()}}scheduleTextFlush(){!this.activeRun||this.activeRun.flushTimer||(this.activeRun.flushTimer=setTimeout(()=>{this.activeRun&&(this.activeRun.flushTimer=null),this.flushTextBuffer()},U))}flushTextBuffer(){if(this.stopTextFlush(),!(!this.activeRun||!this.activeRun.textBuffer)){for(const t of B(this.activeRun.textBuffer))this.activeRun.chunkSeq++,this.callbacks.sendStreamChunk(this.activeRun.eventId,this.activeRun.sessionId,t,this.activeRun.chunkSeq,!1,this.activeRun.clientMsgId);this.activeRun.textBuffer=""}}stopTextFlush(){this.activeRun?.flushTimer&&(clearTimeout(this.activeRun.flushTimer),this.activeRun.flushTimer=null)}startComposing(t,e){}stopComposing(){}resetIdleTimer(){this.lastProgressAt=Date.now(),this.armIdleTimer()}armIdleTimer(){if(this.stopIdleTimer(),this.stopped||!this.activeRun)return;const t=this.lastProgressAt+m-Date.now(),e=Math.max(0,Math.min(q,t)),s=this.activeRun.eventId;this.idleTimer=setTimeout(()=>{this.onIdleTimeout(s)},e)}async onIdleTimeout(t){if(this.stopped||this.activeRun?.eventId!==t)return;if(Date.now()-this.lastProgressAt>=m){this.failIdleRun(t,`liveness budget exhausted after ${m/6e4}min`);return}let s=!1;try{s=await this.ping(H)}catch{s=!1}if(!(this.stopped||this.activeRun?.eventId!==t)){if(s){n.info("openhuman-adapter",`Idle timer: transport healthy, extending until liveness cap (${m/6e4}min): ${t}`),this.armIdleTimer();return}this.failIdleRun(t,"transport unavailable")}}failIdleRun(t,e){this.activeRun?.eventId===t&&(n.error("openhuman-adapter",`Idle timeout (${e}) \u2014 emitting exit for respawn`),this.flushTextBuffer(),this.callbacks.sendEventResult(t,"failed",`idle timeout: ${e}`),this.clearRun(),this.emit("exit",-1))}stopIdleTimer(){this.idleTimer&&(clearTimeout(this.idleTimer),this.idleTimer=null)}resolveWorkspaceDir(){return this.options.workspaceDir?this.options.workspaceDir.replace(/^~/,u()):p(u(),".openhuman")}resolveCwd(){const t=(this.config.options??{}).cwd;return typeof t=="string"&&t?t:process.cwd()}buildPromptText(t){let e=t.text;return t.contextMessages&&t.contextMessages.length>0&&(e=t.contextMessages.map(i=>`[context] ${i.senderId}: ${i.content}`).join(`
|
|
3
3
|
`)+`
|
|
4
4
|
|
|
5
|
-
`+e),this.identity.injectOnce(t.adapterSessionId,e)}buildPromptTextFromEvent(t){let e=t.content||"";if(t.context_messages_json)try{const i=JSON.parse(t.context_messages_json);Array.isArray(i)&&i.length>0&&(e=i.map(
|
|
5
|
+
`+e),this.identity.injectOnce(t.adapterSessionId,e)}buildPromptTextFromEvent(t){let e=t.content||"";if(t.context_messages_json)try{const i=JSON.parse(t.context_messages_json);Array.isArray(i)&&i.length>0&&(e=i.map(a=>`[context] ${a.sender_id??"unknown"}: ${a.content}`).join(`
|
|
6
6
|
`)+`
|
|
7
7
|
|
|
8
|
-
`+e)}catch{}const s=this.identity.injectOnce(t.session_id,e);return this.inboundMessageIds.inject(t.session_id,s,t.msg_id)}}export{
|
|
8
|
+
`+e)}catch{}const s=this.identity.injectOnce(t.session_id,e);return this.inboundMessageIds.inject(t.session_id,s,t.msg_id)}}export{dt as OpenHumanAdapter};
|