grix-connector 3.34.0 → 4.0.0
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/acp/acp-adapter.js +13 -20
- package/dist/adapter/agy/agy-adapter.js +12 -7
- package/dist/adapter/claude/claude-adapter.js +1 -1
- package/dist/adapter/claude/claude-bridge-server.js +1 -1
- package/dist/adapter/claude/claude-tools.js +1 -1
- package/dist/adapter/claude/claude-worker-client.js +1 -1
- package/dist/adapter/claude/mcp-http-launcher.js +2 -2
- package/dist/adapter/claude/result-timeout.js +1 -1
- package/dist/adapter/codewhale/codewhale-adapter.js +3 -6
- package/dist/adapter/codex/codex-bridge.js +11 -9
- package/dist/adapter/cursor/cursor-adapter.js +10 -18
- package/dist/adapter/cursor/session-scanner.js +1 -1
- package/dist/adapter/deepseek-harness/profile-runtime.js +2 -2
- package/dist/adapter/opencode/opencode-adapter.js +7 -5
- package/dist/adapter/openhuman/openhuman-adapter.js +8 -6
- package/dist/adapter/pi/pi-adapter.js +8 -6
- package/dist/adapter/shared/identity-injector.js +2 -4
- package/dist/adapter/shared/inbound-message-id-injector.js +2 -2
- package/dist/assets/dsh-bridge/grix-dsh-bridge-4.0.0.tgz +0 -0
- package/dist/assets/dsh-bridge/manifest.json +6 -6
- package/dist/bridge/adapter-pool.js +1 -1
- package/dist/core/access/allowlist-store.js +1 -1
- package/dist/core/file-ops/list-files.js +1 -1
- package/dist/core/mcp/internal-api-server.js +1 -1
- package/dist/core/observability/sentry-event-dedup.js +1 -0
- package/dist/core/observability/sentry.js +1 -1
- package/dist/core/protocol/message-metadata.js +1 -1
- package/dist/core/protocol/message-reference.js +7 -2
- package/dist/core/protocol/payload-parser.js +3 -4
- package/dist/core/upgrade/npm-upgrader.js +2 -2
- package/dist/core/upgrade/upgrade-checker.js +1 -1
- package/dist/core/util/session-title.js +1 -1
- package/dist/grix.js +1 -1
- package/dist/log.js +2 -2
- package/dist/manager.js +1 -1
- package/dist/mcp/stream-http/config.js +1 -1
- package/dist/mcp/stream-http/connection-binding.js +1 -1
- package/dist/mcp/stream-http/security.js +1 -1
- package/dist/mcp/stream-http/tool-executor.js +1 -1
- package/dist/mcp/stream-http/tool-registry.js +1 -1
- package/dist/mcp/stream-http/tool-schemas.js +1 -1
- package/dist/protocol/acp-client.js +1 -1
- package/openclaw-plugin/index.js +4 -4
- package/package.json +1 -1
- package/scripts/upgrade-guardian.sh +2 -4
- package/dist/assets/dsh-bridge/grix-dsh-bridge-3.34.0.tgz +0 -0
|
@@ -1,23 +1,15 @@
|
|
|
1
|
-
import{execFileSync as
|
|
1
|
+
import{execFileSync as Z}from"node:child_process";import{createInterface as q}from"node:readline";import{EventEmitter as E}from"node:events";import{randomUUID as ee}from"node:crypto";import{mkdirSync as L,readFileSync as P,writeFileSync as D,readdirSync as te,statSync as N}from"node:fs";import{join as A,resolve as se}from"node:path";import{fileURLToPath as ne}from"node:url";import{homedir as F}from"node:os";import{GRIX_PATHS as ie,log as h}from"../../core/log/index.js";import{IdentityInjector as re}from"../shared/identity-injector.js";import{buildSimpleProbeReport as oe}from"../shared/probe-util.js";import{InternalApiServer as ae}from"../../core/mcp/internal-api-server.js";import{resolveMcpServerName as de,pruneLegacyMcpServerEntries as le}from"../../core/mcp/server-name.js";import{InboundMessageIdInjector as ce}from"../shared/inbound-message-id-injector.js";import{insertBeforeTrailingMessageReferences as R}from"../../core/protocol/message-reference.js";import{syncDefaultSkillsToDir as ue}from"../../default-skills/index.js";import{spawnCommand as M,killProcessGroup as I}from"../../core/runtime/spawn.js";import{SessionBindingStore as he}from"../../core/persistence/session-binding-store.js";import{fetchCursorParameterizedModels as me}from"./fetch-models.js";import{resolveCursorSpawn as B}from"./resolve-command.js";import{fetchCursorAccountUsage as fe}from"./fetch-usage.js";import{buildCursorContextWindow as pe,buildCursorContextWindowFromUsage as ge,resolveCursorContextWindowSize as Se}from"./context-window.js";const ye=12e4,x=300*1e3,j=90*1e3,Q=1800*1e3,ve=8192,O="Summarize this conversation for handoff to a fresh chat. Include: goals, key decisions, important file paths, current status, and next steps. Be concise but complete. Output ONLY the summary text.",W=12e4,U=new Set(["completed","failed","error","errored","canceled","cancelled","rejected"]);function _e(d){const e=d.replace(/ToolCall$/i,"").trim();return e?e.charAt(0).toUpperCase()+e.slice(1):d||"tool"}function H(d){const e=String(d.call_id??d.callId??"").trim(),t=d.tool_call??d.toolCall;if(t&&typeof t=="object"&&!Array.isArray(t)){const n=Object.keys(t);if(n.length>0){const s=n[0],i=t[s],r=i&&typeof i=="object"&&!Array.isArray(i)?i:null;return{toolName:_e(s),toolInput:r?.args??r?.arguments??{},callId:e}}}return{toolName:String(d.name??d.tool_name??d.toolName??"tool").trim()||"tool",toolInput:d.args??d.tool_input??d.toolInput??{},callId:e}}function z(d){return String(d??"").trim().toLowerCase().replace(/[^a-z0-9]/g,"")}function J(d){const e=z(d);return e==="askquestion"||e==="askuserquestion"}function G(d){const e=z(d);return e==="createplan"||e==="exitplanmode"}function Ae(d){if(!Array.isArray(d))return;const e=d.map(t=>{if(typeof t=="string")return t.trim();if(!t||typeof t!="object")return"";const n=t,s=String(n.label??"").trim();if(s)return s;const i=String(n.value??"").trim();if(i)return i;const r=String(n.text??"").trim();return r||""}).filter(t=>t.length>0);return e.length>0?e:void 0}function be(d){if(!d||typeof d!="object"||Array.isArray(d))return null;const e=d,t=String(e.title??"").trim(),n=e.questions;if(!Array.isArray(n)||n.length===0)return null;const s=[];for(let i=0;i<n.length;i++){const r=n[i];if(!r||typeof r!="object")continue;const o=r,a=t||`Question ${i+1}`,l=String(o.header??o.id??o.question??a).trim()||a,c=String(o.prompt??o.question??"").trim();if(!c)continue;const u=Ae(o.options),p=o.multiSelect===!0||o.multi_select===!0;s.push({header:l,prompt:c,...u?{options:u}:{},...p?{multi_select:!0}:{}})}return s.length>0?s:null}function ke(d){if(!d||typeof d!="object"||Array.isArray(d))return null;const e=d,t=String(e.name??e.title??"").trim(),n=String(e.overview??"").trim(),s=String(e.plan??"").trim();if(!s&&!n&&!t)return null;const i=[];return t&&i.push(`# ${t}`),n&&i.push(n),s&&i.push(s),i.join(`
|
|
2
2
|
|
|
3
|
-
`)}function
|
|
3
|
+
`)}function we(d){if(!d||typeof d!="object"||Array.isArray(d))return null;const e=d,t=String(e.type??"").trim().toLowerCase();if(!t)return null;switch(t){case"tool_call":{const n=String(e.subtype??"").trim().toLowerCase(),s=H(e);return J(s.toolName)||G(s.toolName)?null:n==="started"||n==="start"?{type:"tool_call",payload:{tool_name:s.toolName,...s.callId?{tool_call_id:s.callId}:{}}}:(U.has(n),null)}case"error":return{type:"error",payload:{message:String(e.message??e.error??e.result??"cursor error").trim()||"cursor error"}};case"result":{const n=String(e.subtype??"").trim().toLowerCase();return e.is_error===!0||n==="error"?{type:"error",payload:{message:String(e.result??e.message??"cursor run failed").trim()||"cursor run failed"}}:null}default:return null}}function Ie(d,e){if(!d||typeof d!="object"||Array.isArray(d))return"";const t=d;if(String(t.type??"")!=="assistant")return"";const n=t.model_call_id??t.modelCallId;if(n!=null&&String(n).trim()!=="")return"";const s=t.message,i=s&&typeof s=="object"&&!Array.isArray(s)?s.content:void 0;if(!Array.isArray(i))return"";const r=i.map(o=>{if(!o||typeof o!="object"||Array.isArray(o))return"";const a=o;return a.type==="text"?String(a.text??""):""}).filter(Boolean).join("");if(!r)return"";if(e){if(r===e||e.startsWith(r)&&r.length<e.length)return"";if(r.startsWith(e))return r.slice(e.length)}return r}const X=[{id:"approval",displayName:"\u4EBA\u5DE5\u786E\u8BA4"},{id:"full_auto",displayName:"\u81EA\u7531\u6A21\u5F0F"},{id:"plan",displayName:"\u8BA1\u5212\u6A21\u5F0F"}];function C(d){const e=String(d??"").trim().toLowerCase();return X.some(t=>t.id===e)?e:null}function T(d){try{return process.kill(d,0),!0}catch(e){return e.code==="EPERM"}}function K(d,e,t){if(t<0)return null;let n;try{n=te(d,{withFileTypes:!0})}catch{return null}for(const s of n){const i=A(d,s.name);if(s.isDirectory()){const r=K(i,e,t-1);if(r)return r}else if(s.isFile()&&s.name.endsWith(e))return i}return null}function V(d,e){if(!d)return null;const t=e||A(F(),".cursor","projects");return K(t,`${d}.jsonl`,6)}function Ce(d){const e=d.context_messages_json;if(e)try{const t=JSON.parse(e);return!Array.isArray(t)||t.length===0?void 0:t.map(n=>({senderId:String(n.sender_id??"unknown"),content:String(n.content??"")}))}catch{return}}function Re(d,e){const t=[String(d??"")],n=String(e.quoted_message_id??"").trim();n&&t.push(`[quoted_message_id] ${n}`);const s=String(e.attachments_json??"").trim();return s&&t.push(`[attachments_json]
|
|
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 T 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 _=C(a.modeId||l.mode)??"full_auto";_==="full_auto"?c.push("--yolo"):_==="plan"&&c.push("--plan");const v=a.cursorSessionId??this.bindingStore?.getAcpSessionId(i.adapterSessionId),b=!!(v&&l.use_continue!==!1&&!t);b&&c.push("--resume",v),c.push(this.buildPromptText(i));const f={...process.env,...this.config.env??{}},y=M(this.config.command,f);h.info("cursor-adapter",`job start: event=${s.event_id} session=${s.session_id} retry=${n}`);let g;try{g=R(y.command,[...y.prefixArgs,...c],{cwd:u||process.cwd(),env:y.env,stdio:["ignore","pipe","pipe"]}).process}catch(S){this.finishActive(o,"failed",`spawn failed: ${S instanceof Error?S.message:String(S)}`);return}this.callbacks.sendEventAck(s.event_id,s.session_id),this.emit("eventStarted",s.event_id,s.session_id);const m={event:s,request:i,handle:r,child:g,seq:0,done:!1,stderr:"",timer:null,idleTimer:null,toolCallsInFlight:new Set,toolCallsStarted:0,assistantTextEmitted:"",streamBuffer:"",lastIoAt:Date.now(),sawResult:!1,retryCount:n,usedContinue:b,workspace:u||process.cwd(),args:c};this.activeBySession.set(o,m),this.notifyBindingReady(o),this.armActiveIdleTimer(o);const k=i.timeoutMs&&i.timeoutMs>0?i.timeoutMs:0;k>0&&(m.timer=setTimeout(()=>{m.done||(I(g,"SIGTERM"),this.finishActive(o,"failed",`cursor agent timeout after ${k}ms`))},k)),$({input:g.stdout}).on("line",S=>this.handleStdoutLineForActive(m,S)),g.stderr?.on("data",S=>{if(m.done)return;const w=m.stderr+String(S??"");m.stderr=w.length>T.STDERR_MAX_CHARS?w.slice(-T.STDERR_MAX_CHARS):w,m.lastIoAt=Date.now(),this.armActiveIdleTimer(o)}),g.once("error",S=>{this.finishActive(o,"failed",`spawn failed: ${String(S?.message??S)}`)}),g.once("close",S=>{if(!m.done)if((S??0)===0)this.finishActive(o,"responded");else{if(this.shouldRetryWithoutContinue(m)){m.timer&&clearTimeout(m.timer),m.idleTimer&&clearTimeout(m.idleTimer),this.activeBySession.delete(o),this.startJob(e,!0,m.retryCount+1);return}const w=m.stderr.trim()||`cursor agent exited with code ${S??-1}`;this.finishActive(o,"failed",w)}})}async cancel(e){const t=this.activeBySession.get(e);t&&(I(t.child,"SIGTERM"),this.finishActive(e,"canceled","canceled"))}deliverInboundEvent(e){const t=String(e.session_id??"").trim();if(!t){h.warn("cursor-adapter",`Dropping event ${e.event_id}: missing session_id`),this.callbacks.sendEventResult(e.event_id,"failed","missing session_id");return}if(!this.alive||this.stopped){h.warn("cursor-adapter",`Dropping event ${e.event_id}: adapter not running`),this.callbacks.sendEventAck(e.event_id,t),this.callbacks.sendEventResult(e.event_id,"failed","adapter not running");return}const n={adapterSessionId:t,text:this.inboundMessageIds.inject(t,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}
|
|
6
|
+
`)}class Y extends E{adapterSessionId;constructor(e){super(),this.adapterSessionId=e}emitDone(e){this.emit("done",e)}emitError(e){if(this.listenerCount("error")===0){h.warn("cursor-adapter",`Prompt handle error (no listeners): ${e.message}`);return}this.emit("error",e)}async cancel(){}}class $ extends E{type="cursor";static STDERR_MAX_CHARS=8192;config;callbacks;alive=!1;stopped=!1;permissionHandler=null;internalApi=null;bindingStore=null;identity;inboundMessageIds=new ce;inboundQueue=[];pendingBySession=new Map;sessions=new Set;sessionRuntime=new Map;sessionSeq=0;lastUsageBySession=new Map;auditCaptures=new Map;completedAuditBoundaries=new Map;lastContextBySession=new Map;pendingHandoffBySession=new Map;pendingQuestionByRequestId=new Map;pendingQuestionAnswerBySession=new Map;cachedAccountRateLimits=null;rateLimitsInflight=null;_availableModels=[];transcriptPathBySession=new Map;activeBySession=new Map;constructor(e,t){super(),this.config=e,this.callbacks=t;const n=e.options??{};this.bindingStore=n.bindingStore instanceof he?n.bindingStore:null,this.identity=new re("cursor-adapter",t.getAgentProfile)}async start(){this.alive=!0,this.stopped=!1,(this.config.options??{}).mcp_tools!==!1&&(this.internalApi=new ae,this.internalApi.setInvokeHandler(async(t,n,s,i)=>this.callbacks.agentInvoke(t,n,i)),await this.internalApi.start()),this.notifyBindingReady(),this.refreshModels().then(()=>this.notifyBindingReady()).catch(()=>{}),this.refreshAccountRateLimits().then(()=>this.notifyBindingReady()).catch(()=>{})}get availableModels(){return this._availableModels}get availableModes(){return X.map(e=>({...e}))}async refreshModels(){try{const e={...process.env,...this.config.env??{}},t=await me({command:this.config.command,env:e,cwd:process.cwd()});t.length>0&&(this._availableModels=t,h.info("cursor-adapter",`Loaded ${t.length} available models`))}catch(e){h.warn("cursor-adapter",`Failed to refresh models: ${e instanceof Error?e.message:String(e)}`)}}async stop(){this.stopped=!0,this.alive=!1;for(const e of this.activeBySession.values())I(e.child,"SIGTERM");this.activeBySession.clear(),this.transcriptPathBySession.clear(),this.internalApi&&(await this.internalApi.stop(),this.internalApi=null)}isAlive(){return this.alive}async createSession(e){const t=`cursor-${Date.now()}-${++this.sessionSeq}`;return this.sessions.add(t),this.sessionRuntime.set(t,{cwd:typeof e.cwd=="string"?e.cwd:void 0,modelId:typeof e.modelId=="string"?e.modelId:void 0,modeId:typeof e.modeId=="string"?e.modeId:void 0}),t}async resumeSession(e,t){this.sessions.add(e);const n=this.sessionRuntime.get(e)??{};this.sessionRuntime.set(e,{...n,cwd:typeof t?.cwd=="string"?t.cwd:n.cwd,modelId:typeof t?.modelId=="string"?t.modelId:n.modelId,modeId:typeof t?.modeId=="string"?t.modeId:n.modeId})}onAgentProfileChanged(){this.identity.onProfileChanged()}async destroySession(e){this.sessions.delete(e),this.sessionRuntime.delete(e),this.transcriptPathBySession.delete(e),this.identity.forgetSession(e),this.inboundMessageIds.forgetSession(e),this.pendingQuestionAnswerBySession.delete(e),this.pendingHandoffBySession.delete(e);for(const[t,n]of this.pendingQuestionByRequestId)n.sessionId===e&&this.pendingQuestionByRequestId.delete(t)}sendPrompt(e){const t=new Y(e.adapterSessionId);if(!this.alive||this.stopped)return queueMicrotask(()=>t.emitError(new Error("adapter not running"))),t;const n=this.inboundQueue.shift()??{event_id:`cursor-evt-${Date.now()}`,session_id:e.adapterSessionId,content:e.text},s=e.adapterSessionId,i=this.pendingBySession.get(s)??[];return i.push({event:n,request:e,handle:t}),this.pendingBySession.set(s,i),this.tryStartNext(s),t}tryStartNext(e){if(this.activeBySession.has(e)||!this.alive||this.stopped)return;const t=this.pendingBySession.get(e);if(!t||t.length===0)return;const n=t.shift();t.length===0?this.pendingBySession.delete(e):this.pendingBySession.set(e,t),n&&this.startJob(n,!1,0)}startJob(e,t,n){const{event:s,request:i,handle:r}=e;s.audit?.enabled&&!this.auditCaptures.has(s.event_id)&&this.auditCaptures.set(s.event_id,[]);const o=i.adapterSessionId,a=this.sessionRuntime.get(i.adapterSessionId)??{},l=this.config.options??{},c=[...this.config.args??[]];c.push("-p","--output-format","stream-json"),l.trust!==!1&&c.push("--trust");const u=a.cwd||l.workspace;this.internalApi&&u&&(this.ensureWorkspaceMcpAndSkills(u),c.push("--approve-mcps")),u&&c.push("--workspace",u);const p=a.modelId||l.model;p&&c.push("--model",p);const _=C(a.modeId||l.mode)??"full_auto";_==="full_auto"?c.push("--yolo"):_==="plan"&&c.push("--plan");const v=a.cursorSessionId??this.bindingStore?.getAcpSessionId(i.adapterSessionId),b=!!(v&&l.use_continue!==!1&&!t);b&&c.push("--resume",v),c.push(this.buildPromptText(i));const f={...process.env,...this.config.env??{}},y=B(this.config.command,f);h.info("cursor-adapter",`job start: event=${s.event_id} session=${s.session_id} retry=${n}`);let g;try{g=M(y.command,[...y.prefixArgs,...c],{cwd:u||process.cwd(),env:y.env,stdio:["ignore","pipe","pipe"]}).process}catch(S){this.finishActive(o,"failed",`spawn failed: ${S instanceof Error?S.message:String(S)}`);return}this.callbacks.sendEventAck(s.event_id,s.session_id),this.emit("eventStarted",s.event_id,s.session_id);const m={event:s,request:i,handle:r,child:g,seq:0,done:!1,stderr:"",timer:null,idleTimer:null,toolCallsInFlight:new Set,toolCallsStarted:0,assistantTextEmitted:"",streamBuffer:"",lastIoAt:Date.now(),sawResult:!1,retryCount:n,usedContinue:b,workspace:u||process.cwd(),args:c};this.activeBySession.set(o,m),this.notifyBindingReady(o),this.armActiveIdleTimer(o);const k=i.timeoutMs&&i.timeoutMs>0?i.timeoutMs:0;k>0&&(m.timer=setTimeout(()=>{m.done||(I(g,"SIGTERM"),this.finishActive(o,"failed",`cursor agent timeout after ${k}ms`))},k)),q({input:g.stdout}).on("line",S=>this.handleStdoutLineForActive(m,S)),g.stderr?.on("data",S=>{if(m.done)return;const w=m.stderr+String(S??"");m.stderr=w.length>$.STDERR_MAX_CHARS?w.slice(-$.STDERR_MAX_CHARS):w,m.lastIoAt=Date.now(),this.armActiveIdleTimer(o)}),g.once("error",S=>{this.finishActive(o,"failed",`spawn failed: ${String(S?.message??S)}`)}),g.once("close",S=>{if(!m.done)if((S??0)===0)this.finishActive(o,"responded");else{if(this.shouldRetryWithoutContinue(m)){m.timer&&clearTimeout(m.timer),m.idleTimer&&clearTimeout(m.idleTimer),this.activeBySession.delete(o),this.startJob(e,!0,m.retryCount+1);return}const w=m.stderr.trim()||`cursor agent exited with code ${S??-1}`;this.finishActive(o,"failed",w)}})}async cancel(e){const t=this.activeBySession.get(e);t&&(I(t.child,"SIGTERM"),this.finishActive(e,"canceled","canceled"))}deliverInboundEvent(e){const t=String(e.session_id??"").trim();if(!t){h.warn("cursor-adapter",`Dropping event ${e.event_id}: missing session_id`),this.callbacks.sendEventResult(e.event_id,"failed","missing session_id");return}if(!this.alive||this.stopped){h.warn("cursor-adapter",`Dropping event ${e.event_id}: adapter not running`),this.callbacks.sendEventAck(e.event_id,t),this.callbacks.sendEventResult(e.event_id,"failed","adapter not running");return}const n={adapterSessionId:t,text:this.inboundMessageIds.inject(t,Re(String(e.content??""),e),e.msg_id),contextMessages:Ce(e)},s=new Y(t),i=this.pendingBySession.get(t)??[];i.push({event:e,request:n,handle:s}),this.pendingBySession.set(t,i),h.info("cursor-adapter",`inbound queued: event=${e.event_id} session=${t} depth=${i.length}`),this.tryStartNext(t)}cancelSessionWork(e,t){const n=this.pendingBySession.get(e)??[];this.pendingBySession.delete(e);const s=this.activeBySession.get(e);s&&(this.killChildWithFallback(s.child),this.finishActive(e,"canceled",t));for(const i of n)this.callbacks.sendEventAck(i.event.event_id,i.event.session_id),this.callbacks.sendEventResult(i.event.event_id,"canceled",t),i.handle.emitDone({status:"canceled",error:t})}killChildWithFallback(e){I(e,"SIGTERM"),setTimeout(()=>{if(e.exitCode===null&&e.signalCode===null)try{I(e,"SIGKILL")}catch{}},5e3).unref()}deliverStopEvent(e,t){if(t){const n=this.activeBySession.get(t);if(n?.event.event_id===e){this.killChildWithFallback(n.child),this.finishActive(t,"canceled","stopped");return}const s=this.pendingBySession.get(t)??[],i=s.findIndex(r=>r.event.event_id===e);if(i>=0){const[r]=s.splice(i,1);s.length===0?this.pendingBySession.delete(t):this.pendingBySession.set(t,s),this.callbacks.sendEventAck(r.event.event_id,r.event.session_id),this.callbacks.sendEventResult(r.event.event_id,"canceled","stopped"),r.handle.emitDone({status:"canceled",error:"stopped"})}return}for(const[n,s]of this.activeBySession.entries())if(s.event.event_id===e){this.killChildWithFallback(s.child),this.finishActive(n,"canceled","stopped");return}for(const[n,s]of this.pendingBySession.entries()){const i=s.findIndex(o=>o.event.event_id===e);if(i<0)continue;const[r]=s.splice(i,1);s.length===0?this.pendingBySession.delete(n):this.pendingBySession.set(n,s),this.callbacks.sendEventAck(r.event.event_id,r.event.session_id),this.callbacks.sendEventResult(r.event.event_id,"canceled","stopped"),r.handle.emitDone({status:"canceled",error:"stopped"});return}}async handleLocalAction(e){const t=String(e.action_type??"").trim().toLowerCase(),n=e.params??{},s=String(n.session_id??"").trim(),i=e.action_id;if(t==="claude_interaction_reply")return String(n.kind??"").trim()==="permission"?(this.callbacks.sendLocalActionResult(i,"failed",void 0,"unsupported","Cursor adapter does not support interactive permission approvals"),{handled:!0,kind:"permission_unsupported"}):this.handleQuestionReply(e);switch(t){case"set_model":{const r=String(n.model_id??"").trim();if(!r||!s)return this.callbacks.sendLocalActionResult(i,"failed",void 0,"invalid_params","model_id and session_id are required"),{handled:!0,kind:"set_model"};const o=this.sessionRuntime.get(s)??{};this.sessionRuntime.set(s,{...o,modelId:r}),this.notifyBindingReady(s);const a=this.getToolbarMeta(s);return this.callbacks.sendLocalActionResult(i,"ok",{outcome:"model_set",session_context:{model_id:r,mode_id:a.mode_id,modelId:r,modeId:a.mode_id},model_id:r,mode_id:a.mode_id,available_models:this.availableModels,available_modes:this.availableModes}),{handled:!0,kind:"set_model"}}case"set_mode":{const r=C(n.mode_id);if(!r||!s)return this.callbacks.sendLocalActionResult(i,"failed",void 0,"invalid_params","mode_id must be approval, full_auto, or plan and session_id is required"),{handled:!0,kind:"set_mode"};const o=this.sessionRuntime.get(s)??{};this.sessionRuntime.set(s,{...o,modeId:r}),this.notifyBindingReady(s);const a=this.getToolbarMeta(s);return this.callbacks.sendLocalActionResult(i,"ok",{outcome:"mode_set",session_context:{model_id:a.model_id,mode_id:r,modelId:a.model_id,modeId:r},model_id:a.model_id,mode_id:r,available_models:this.availableModels,available_modes:this.availableModes}),{handled:!0,kind:"set_mode"}}case"get_context":{const r=s?this.sessionRuntime.get(s):void 0,o=this.config.options??{},a=this.getToolbarMeta(s);return this.callbacks.sendLocalActionResult(i,"ok",{session_context:{model_id:a.model_id,mode_id:a.mode_id,cwd:r?.cwd??o.workspace??null},model_id:a.model_id,mode_id:a.mode_id,modelId:a.model_id,modeId:a.mode_id,cwd:r?.cwd??o.workspace??null,available_models:this.availableModels,available_modes:this.availableModes}),{handled:!0,kind:"get_context"}}case"get_rate_limits":{await this.refreshAccountRateLimits({force:!0});const r=this.getRateLimitsSnapshot(s||void 0);return this.callbacks.sendLocalActionResult(i,"ok",r),s&&this.notifyBindingReady(s),{handled:!0,kind:"get_rate_limits"}}case"get_session_usage":{const r=s?this.getUsageSnapshot(s):null;if(r){const o={inputTokens:r.total.input,outputTokens:r.total.output,cacheReadInputTokens:r.total.cacheRead,cacheCreationInputTokens:r.total.cacheWrite};this.callbacks.sendLocalActionResult(i,"ok",{sessionId:s,adapterType:"cursor",sampledAt:r.sampledAt,turns:r.turns,total:o,models:[{model:this.getToolbarMeta(s).model_id??"auto",turns:r.turns,total:o}]})}else this.callbacks.sendLocalActionResult(i,"failed",void 0,"usage_not_found","No usage data found for this session");return{handled:!0,kind:"get_session_usage"}}case"session_control":{const r=String(n.verb??"").trim().toLowerCase();if(r==="restart"&&s)this.cancelSessionWork(s,"restarted"),this.callbacks.sendLocalActionResult(i,"ok",{outcome:"restarted"});else if(r==="status"){const o=this.sessionRuntime.get(s),a=this.config.options??{},l=this.getToolbarMeta(s),c=this.activeBySession.has(s);this.callbacks.sendLocalActionResult(i,"ok",{verb:"status",status:c?"running":"idle",session_context:{model_id:l.model_id,mode_id:l.mode_id,cwd:o?.cwd??a.workspace??null},model_id:l.model_id,mode_id:l.mode_id,modelId:l.model_id,modeId:l.mode_id,cwd:o?.cwd??a.workspace??null,available_models:this.availableModels,available_modes:this.availableModes})}else this.callbacks.sendLocalActionResult(i,"failed",void 0,"invalid_verb",`Unsupported verb: ${r}`);return{handled:!0,kind:"session_control"}}default:return{handled:!1,kind:"unsupported"}}}getSupportedCommands(){return[{name:"compact",description:"\u538B\u7F29\u4E0A\u4E0B\u6587",args:"[instructions]"}]}async execCommand(e,t,n){return e!=="compact"?{status:"unsupported",message:`Unsupported command: ${e}`}:this.runCompactHandoff(n,t)}async runCompactHandoff(e,t=""){if(!e)return{status:"failed",message:"session_id is required"};if(this.activeBySession.has(e))return{status:"failed",message:"Cannot compact while a Cursor turn is active"};const n=this.sessionRuntime.get(e)??{},s=this.config.options??{},i=n.cursorSessionId??this.bindingStore?.getAcpSessionId(e)??null;if(!i)return{status:"failed",message:"No Cursor chat to compact (send a message first)"};const r=String(n.cwd||s.workspace||process.cwd()),o=String(n.modelId||s.model||""),a=C(n.modeId||s.mode)??"full_auto",l=t.trim(),c=l?`${O}
|
|
7
7
|
|
|
8
8
|
Additional focus from user:
|
|
9
|
-
${l}`:
|
|
10
|
-
`)
|
|
11
|
-
|
|
12
|
-
[
|
|
13
|
-
${e.text}`;const n=this.pendingQuestionAnswerBySession.get(e.adapterSessionId);n&&(this.pendingQuestionAnswerBySession.delete(e.adapterSessionId),t=`[User answered a previous agent question]
|
|
14
|
-
${n}
|
|
15
|
-
|
|
16
|
-
[Current user message]
|
|
17
|
-
${t}`);const s=this.pendingHandoffBySession.get(e.adapterSessionId);return s&&(this.pendingHandoffBySession.delete(e.adapterSessionId),t=`[Previous conversation summary \u2014 continue from this handoff]
|
|
18
|
-
${s}
|
|
19
|
-
|
|
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)):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 m=this.sessionRuntime.get(e.request.adapterSessionId)??{};m.cursorSessionId!==c&&this.transcriptPathBySession.delete(e.request.adapterSessionId),this.sessionRuntime.set(e.request.adapterSessionId,{...m,cursorSessionId:c}),this.bindingStore?.setAcpSessionId(e.request.adapterSessionId,c)}const u=i?.usage??{},p=this.lastUsageBySession.get(e.request.adapterSessionId);this.lastUsageBySession.set(e.request.adapterSessionId,{sampledAt:new Date().toISOString(),turns:(p?.turns??0)+1,total:{input:(p?.total.input??0)+Number(u.inputTokens??0),output:(p?.total.output??0)+Number(u.outputTokens??0),cacheRead:(p?.total.cacheRead??0)+Number(u.cacheReadTokens??0),cacheWrite:(p?.total.cacheWrite??0)+Number(u.cacheWriteTokens??0)}});const _=String((this.sessionRuntime.get(e.request.adapterSessionId)?.modelId??this.config.options?.model??"")||""),v=Math.max(1,(e.toolCallsStarted??0)+1),b=Number(u.inputTokens??0),f=Number(u.cacheReadTokens??0),y=Number(u.cacheWriteTokens??0),g=pe({inputTokens:b,cacheReadTokens:f,cacheWriteTokens:y},_,{apiRounds:v});g&&(this.lastContextBySession.set(e.request.adapterSessionId,g),h.info("cursor-adapter",`[context_window] used=${g.usedTokens} size=${g.sizeTokens} pct=${g.usedPercentage.toFixed(1)}% model=${_||"auto"} rawIn=${b} rawCache=${f} rawWrite=${y} apiRounds=${v}`))}}handleSpecialToolCallStarted(e,t){const n=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+x}),h.info("cursor-adapter",`Sent agent_question card: request_id=${i} questions=${s.length} event=${n.event_id}`)}async handleQuestionReply(e){const t=e.params??{},n=String(t.request_id??"").trim();if(!n)return this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"request_id_required","request_id is required"),{handled:!0,kind:"question_reply_no_id"};const s=this.pendingQuestionByRequestId.get(n),i=s?Date.now()-s.createdAt>x+3e4:!0;if(!s||i)return this.pendingQuestionByRequestId.delete(n),this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"interaction_request_not_pending","The question is no longer pending; the reply was not delivered."),{handled:!0,kind:"question_reply_not_pending"};const r=t.resolution??{},o=String(r.type??"");if(o==="action"&&String(r.value??"")==="cancel")return this.pendingQuestionByRequestId.delete(n),this.callbacks.sendLocalActionResult(e.action_id,"ok",{request_id:n,resolution:"cancel"}),{handled:!0,kind:"question_reply_cancel"};let a="";return o==="text"?a=String(r.value??""):o==="map"&&(a=(Array.isArray(r.entries)?r.entries:[]).map(c=>c.value).join(", ")),a?(this.pendingQuestionByRequestId.delete(n),this.pendingQuestionAnswerBySession.set(s.sessionId,a),this.callbacks.sendLocalActionResult(e.action_id,"ok",{request_id:n,resolution:"answer"}),h.info("cursor-adapter",`Question reply cached for next prompt: request_id=${n} session=${s.sessionId}`),{handled:!0,kind:"question_reply"}):(this.pendingQuestionByRequestId.delete(n),this.callbacks.sendLocalActionResult(e.action_id,"ok",{request_id:n,resolution:"cancel"}),{handled:!0,kind:"question_reply_empty"})}enqueueAssistantStreamDelta(e,t){t&&(e.assistantTextEmitted+=t,e.streamBuffer+=t,e.streamBuffer.length>=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>x){this.pendingQuestionByRequestId.delete(n);continue}return!0}return!1}armActiveIdleTimer(e){const t=this.activeBySession.get(e);!t||t.done||(t.idleTimer&&clearTimeout(t.idleTimer),t.idleTimer=setTimeout(()=>{if(t.done)return;if(this.hasPendingQuestionForSession(e)){h.info("cursor-adapter",`Idle timeout skipped: awaiting AskQuestion for ${t.event.event_id}, resetting timer`),this.armActiveIdleTimer(e);return}if(this.shouldExtendByLiveness(e)){this.armActiveIdleTimer(e);return}if(t.sawResult){h.warn("cursor-adapter",`Idle after result with no close \u2014 finishing as responded: event=${t.event.event_id} session=${e}`),this.killChildWithFallback(t.child),this.finishActive(e,"responded");return}const n=t.child?.pid,i=!!(n&&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"],windowsHide:!0}).toString("utf8").trim()||null}catch{return null}}writeCursorMcpConfig(e,t,n,s){try{const i=A(e,".cursor"),r=A(i,"mcp.json");E(i,{recursive:!0});let o={};try{o=JSON.parse(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,T as CursorAdapter,we as extractCursorAssistantStreamDelta,K as findCursorTranscript,be as formatCursorCreatePlanText,z as isCursorAskQuestionTool,J as isCursorCreatePlanTool,ke as mapCursorStreamJsonToRawEnvelope,Ae as parseCursorAskQuestionPayload};
|
|
9
|
+
${l}`:O;this.emit("pauseIntake","compaction"),h.info("cursor-adapter",`[compact] summarize resume=${i} session=${e}`);try{const u=await this.runPrintOnce({prompt:c,resumeChatId:i,cwd:r,modelId:o||void 0,modeId:a,timeoutMs:W});if(!u.ok||!u.text.trim())return{status:"failed",message:u.error??"Summarize failed (empty result)"};const p=await this.createEmptyChat();return p?(this.sessionRuntime.set(e,{...n,cwd:r,modelId:o||n.modelId,modeId:a,cursorSessionId:p}),this.transcriptPathBySession.delete(e),this.bindingStore?.setAcpSessionId(e,p),this.identity.forgetSession(e),this.inboundMessageIds.forgetSession(e),this.pendingHandoffBySession.set(e,u.text.trim()),this.lastContextBySession.set(e,pe(0,Se(o))),this.notifyBindingReady(e),h.info("cursor-adapter",`[compact] handoff ready: old=${i} new=${p} summaryChars=${u.text.trim().length}`),{status:"ok",message:"Context compacted via summarize + new chat",data:{previousChatId:i,chatId:p}}):{status:"failed",message:"agent create-chat failed"}}finally{this.emit("resumeIntake","compaction")}}runPrintOnce(e){const t={...process.env,...this.config.env??{}},n=B(this.config.command,t),s=[...n.prefixArgs,...this.config.args??[]];s.push("-p","--output-format","stream-json"),this.config.options?.trust!==!1&&s.push("--trust"),e.cwd&&s.push("--workspace",e.cwd),e.modelId&&s.push("--model",e.modelId);const i=C(e.modeId)??"full_auto";return i==="full_auto"?s.push("--yolo"):i==="plan"&&s.push("--plan"),e.resumeChatId&&s.push("--resume",e.resumeChatId),s.push(e.prompt),new Promise(r=>{let o;try{o=M(n.command,s,{cwd:e.cwd||process.cwd(),env:n.env,stdio:["ignore","pipe","pipe"]}).process}catch(f){r({ok:!1,text:"",error:`spawn failed: ${f instanceof Error?f.message:String(f)}`});return}let a="",l="",c="",u=!1;const p=e.timeoutMs&&e.timeoutMs>0?e.timeoutMs:W,_=setTimeout(()=>{u||(u=!0,this.killChildWithFallback(o),r({ok:!1,text:a,sessionId:l||void 0,error:`compact timeout after ${p}ms`}))},p);_.unref?.();const v=(f,y)=>{u||(u=!0,clearTimeout(_),r({ok:f,text:a,sessionId:l||void 0,error:y}))};q({input:o.stdout}).on("line",f=>{const y=f.trim();if(!y)return;let g;try{g=JSON.parse(y)}catch{return}if(g?.type!=="result")return;const m=typeof g?.result=="string"?g.result:"";m&&(a=m);const k=String(g?.session_id??"").trim();k&&(l=k)}),o.stderr?.on("data",f=>{c+=typeof f=="string"?f:f.toString("utf8")}),o.on("error",f=>v(!1,f.message)),o.on("close",f=>{if(!u){if(a.trim()){v(!0);return}v(!1,c.trim()||`cursor agent exited with code ${f??"unknown"}`)}})})}createEmptyChat(){const e={...process.env,...this.config.env??{}},t=B(this.config.command,e);return new Promise(n=>{let s;try{s=M(t.command,[...t.prefixArgs,"create-chat"],{cwd:process.cwd(),env:t.env,stdio:["ignore","pipe","pipe"]}).process}catch{n(null);return}let i="",r=!1;const o=setTimeout(()=>{r||(r=!0,this.killChildWithFallback(s),n(null))},3e4);o.unref?.(),s.stdout?.on("data",a=>{i+=typeof a=="string"?a:a.toString("utf8")}),s.on("error",()=>{r||(r=!0,clearTimeout(o),n(null))}),s.on("close",a=>{if(r)return;r=!0,clearTimeout(o);const l=i.trim().split(/\s+/)[0]??"";n(a===0&&/^[0-9a-f-]{16,}$/i.test(l)?l:null)})})}setPermissionHandler(e){this.permissionHandler=e}async ping(e){if(!this.alive||this.stopped||(this.config.options??{}).mcp_tools!==!1&&this.internalApi===null)return!1;for(const n of this.activeBySession.values()){const s=n.child?.pid;if(s&&!T(s))return!1}return!0}getStatus(){let e=0;for(const t of this.pendingBySession.values())e+=t.length;return{alive:this.alive,busy:this.activeBySession.size>0,sessions:this.sessions.size,details:{queueDepth:e+this.inboundQueue.length,activeSessions:this.activeBySession.size}}}getActiveEventIds(){const e=[];for(const t of this.activeBySession.values())t.event.event_id&&e.push(t.event.event_id);return e}clearActiveEventForShutdown(){for(const[e,t]of this.activeBySession)this.snapshotAuditBoundary(t.event.event_id,e);this.activeBySession.clear()}takeAuditBoundary(e){const t=this.completedAuditBoundaries.get(e);return t&&this.completedAuditBoundaries.delete(e),t}getMcpConfig(){return null}async probe(e){const t=this.getStatus();return{...await oe(this.config.command||"cursor-agent",{alive:t.alive,busy:t.busy,started:this.alive},e),session:this.probeSessionRecord()}}probeSessionRecord(){const e=this.firstKnownCursorSessionId(),t=e?V(e):null;if(!t)return{recordPath:null,lastActivityMs:null,freshMs:null};try{const n=N(t);return{recordPath:t,lastActivityMs:n.mtimeMs,freshMs:Date.now()-n.mtimeMs}}catch{return{recordPath:t,lastActivityMs:null,freshMs:null}}}firstKnownCursorSessionId(){for(const e of this.activeBySession.keys()){const t=this.sessionRuntime.get(e);if(t?.cursorSessionId)return t.cursorSessionId}for(const e of this.sessionRuntime.values())if(e.cursorSessionId)return e.cursorSessionId;return null}getUsageSnapshot(e){return this.lastUsageBySession.get(e)??null}getRateLimitsSnapshot(e){const t=this.cachedAccountRateLimits,n=e?this.lastContextBySession.get(e)??null:null;if(!t)return{adapterType:"cursor",available:!1,cached:!1,sampledAt:null,rateLimits:null,contextWindow:n,tokenUsage:null};const s=Date.parse(t.sampledAt);return{adapterType:"cursor",available:t.available&&!!t.rateLimits,cached:!0,sampledAt:Number.isFinite(s)?s:Date.now(),rateLimits:t.rateLimits,contextWindow:n,tokenUsage:null,...t.displayMessage?{displayMessage:t.displayMessage}:{},...t.error?{error:t.error}:{}}}async refreshAccountRateLimits(e){const t=e?.force===!0,n=this.cachedAccountRateLimits,s=n?Date.now()-Date.parse(n.sampledAt):Number.POSITIVE_INFINITY;if(!t&&n&&Number.isFinite(s)&&s<6e4||this.rateLimitsInflight&&(await this.rateLimitsInflight,!t))return;const i=(async()=>{const r=await fe();this.cachedAccountRateLimits={available:r.available,sampledAt:r.sampledAt,rateLimits:r.rateLimits,displayMessage:r.displayMessage,error:r.error},r.available&&r.rateLimits?h.info("cursor-adapter",`[rate-limits] monthly=${r.rateLimits.fiveHour.usedPercentage.toFixed(1)}% api=${r.rateLimits.sevenDay.usedPercentage.toFixed(1)}% resetsAt=${r.rateLimits.fiveHour.resetsAt}`+(r.displayMessage?` msg=${r.displayMessage}`:"")):h.warn("cursor-adapter",`[rate-limits] unavailable: ${r.error??"unknown"}`)})();this.rateLimitsInflight=i.finally(()=>{this.rateLimitsInflight=null}),await this.rateLimitsInflight}getToolbarMeta(e){const t=this.config.options??{},n=e||String(t.aibotSessionId??""),s=n?this.sessionRuntime.get(n):void 0,i=String(s?.modelId||t.model||"auto"),r=C(s?.modeId||t.mode)??"full_auto",o={model_id:i,mode_id:r,currentModelId:i,currentModeId:r,available_models:this.availableModels,available_modes:this.availableModes},a=this.cachedAccountRateLimits?.rateLimits;if(a?.fiveHour||a?.sevenDay){const c=Date.parse(this.cachedAccountRateLimits?.sampledAt??"")||Date.now();o.rate_limits={...a.fiveHour?{fiveHour:a.fiveHour}:{},...a.sevenDay?{sevenDay:a.sevenDay}:{},sampledAt:c}}const l=n?this.lastContextBySession.get(n):void 0;return l&&(o.context_window={usedPercentage:l.usedPercentage,remainingPercentage:l.remainingPercentage,usedTokens:l.usedTokens,sizeTokens:l.sizeTokens}),o}notifyBindingReady(e){if(!this.callbacks.sendUpdateBindingCard)return;const t=this.config.options??{},n=e||String(t.aibotSessionId??"");if(!n)return;const s=this.sessionRuntime.get(n),i=String(s?.cwd||t.workspace||"");i&&this.callbacks.sendUpdateBindingCard(n,this.activeBySession.has(n)?"busy":"ready",i,this.getToolbarMeta(n))}buildPromptText(e){let t;if(!e.contextMessages||e.contextMessages.length===0)t=e.text;else{const i=e.contextMessages.map(r=>`[${r.senderId}] ${r.content}`).join(`
|
|
10
|
+
`);t=R(e.text,`[Conversation context]
|
|
11
|
+
${i}`)}const n=this.pendingQuestionAnswerBySession.get(e.adapterSessionId);n&&(this.pendingQuestionAnswerBySession.delete(e.adapterSessionId),t=R(t,`[User answered a previous agent question]
|
|
12
|
+
${n}`));const s=this.pendingHandoffBySession.get(e.adapterSessionId);return s&&(this.pendingHandoffBySession.delete(e.adapterSessionId),t=R(t,`[Previous conversation summary \u2014 continue from this handoff]
|
|
13
|
+
${s}`)),this.identity.injectOnce(e.adapterSessionId,t)}handleStdoutLineForActive(e,t){if(e.done)return;const n=t.trim();if(!n)return;e.lastIoAt=Date.now();const{event:s}=e;let i;try{i=JSON.parse(n)}catch{this.armActiveIdleTimer(e.request.adapterSessionId);return}const r=this.auditCaptures.get(s.event_id);if(r&&i&&typeof i=="object"&&r.push({sequence:r.length,observedAt:new Date().toISOString(),event:structuredClone(i)}),i?.type==="tool_call"){const l=String(i?.call_id??""),c=String(i?.subtype??"");l&&(c==="started"||c==="start"?(this.flushAssistantStreamBuffer(e),e.toolCallsInFlight.add(l),e.toolCallsStarted+=1,this.handleSpecialToolCallStarted(e,i)):U.has(c)&&e.toolCallsInFlight.delete(l))}this.armActiveIdleTimer(e.request.adapterSessionId);const o=we(i);o&&this.callbacks.sendRawEventEnvelope?.(s.event_id,s.session_id,o);const a=Ie(i,e.assistantTextEmitted);if(a&&this.enqueueAssistantStreamDelta(e,a),i?.type==="result"){e.sawResult=!0;const l=typeof i?.result=="string"?i.result.trim():"";l&&(e.assistantTextEmitted?l.startsWith(e.assistantTextEmitted)&&l.length>e.assistantTextEmitted.length&&this.enqueueAssistantStreamDelta(e,l.slice(e.assistantTextEmitted.length)):this.enqueueAssistantStreamDelta(e,l)),this.flushAssistantStreamBuffer(e);const c=String(i?.session_id??"").trim();if(c){const m=this.sessionRuntime.get(e.request.adapterSessionId)??{};m.cursorSessionId!==c&&this.transcriptPathBySession.delete(e.request.adapterSessionId),this.sessionRuntime.set(e.request.adapterSessionId,{...m,cursorSessionId:c}),this.bindingStore?.setAcpSessionId(e.request.adapterSessionId,c)}const u=i?.usage??{},p=this.lastUsageBySession.get(e.request.adapterSessionId);this.lastUsageBySession.set(e.request.adapterSessionId,{sampledAt:new Date().toISOString(),turns:(p?.turns??0)+1,total:{input:(p?.total.input??0)+Number(u.inputTokens??0),output:(p?.total.output??0)+Number(u.outputTokens??0),cacheRead:(p?.total.cacheRead??0)+Number(u.cacheReadTokens??0),cacheWrite:(p?.total.cacheWrite??0)+Number(u.cacheWriteTokens??0)}});const _=String((this.sessionRuntime.get(e.request.adapterSessionId)?.modelId??this.config.options?.model??"")||""),v=Math.max(1,(e.toolCallsStarted??0)+1),b=Number(u.inputTokens??0),f=Number(u.cacheReadTokens??0),y=Number(u.cacheWriteTokens??0),g=ge({inputTokens:b,cacheReadTokens:f,cacheWriteTokens:y},_,{apiRounds:v});g&&(this.lastContextBySession.set(e.request.adapterSessionId,g),h.info("cursor-adapter",`[context_window] used=${g.usedTokens} size=${g.sizeTokens} pct=${g.usedPercentage.toFixed(1)}% model=${_||"auto"} rawIn=${b} rawCache=${f} rawWrite=${y} apiRounds=${v}`))}}handleSpecialToolCallStarted(e,t){const n=H(t);if(G(n.toolName)){const s=ke(n.toolInput);s?(e.seq+=1,this.callbacks.sendStreamChunk(e.event.event_id,e.event.session_id,s,e.seq,!1),h.info("cursor-adapter",`CreatePlan forwarded as stream text: event=${e.event.event_id} chars=${s.length}`)):(e.seq+=1,this.callbacks.sendStreamChunk(e.event.event_id,e.event.session_id,"[CreatePlan] \u65B9\u6848\u5185\u5BB9\u89E3\u6790\u5931\u8D25\uFF0C\u672A\u4E0B\u53D1\u6B63\u6587\u3002",e.seq,!1),h.warn("cursor-adapter",`CreatePlan missing plan body: event=${e.event.event_id}`));return}J(n.toolName)&&this.emitAskQuestionCard(e,n.toolInput)}emitAskQuestionCard(e,t){const n=e.event,s=be(t);if(!s){e.seq+=1,this.callbacks.sendStreamChunk(n.event_id,n.session_id,"[AskQuestion] \u95EE\u9898\u5185\u5BB9\u89E3\u6790\u5931\u8D25\uFF0C\u672A\u4E0B\u53D1\u95EE\u9898\u5361\u3002",e.seq,!1),h.warn("cursor-adapter",`AskQuestion parse failed: event=${n.event_id}`);return}if(!this.callbacks.sendAgentQuestionCard){h.warn("cursor-adapter","AskQuestion skipped: sendAgentQuestionCard callback not wired");return}const i=ee(),r=Date.now();this.pendingQuestionByRequestId.set(i,{eventId:n.event_id,sessionId:n.session_id,createdAt:r}),this.callbacks.sendAgentQuestionCard(n.event_id,n.session_id,{request_id:i,mode:"form",questions:s,expires_at:r+x}),h.info("cursor-adapter",`Sent agent_question card: request_id=${i} questions=${s.length} event=${n.event_id}`)}async handleQuestionReply(e){const t=e.params??{},n=String(t.request_id??"").trim();if(!n)return this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"request_id_required","request_id is required"),{handled:!0,kind:"question_reply_no_id"};const s=this.pendingQuestionByRequestId.get(n),i=s?Date.now()-s.createdAt>x+3e4:!0;if(!s||i)return this.pendingQuestionByRequestId.delete(n),this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"interaction_request_not_pending","The question is no longer pending; the reply was not delivered."),{handled:!0,kind:"question_reply_not_pending"};const r=t.resolution??{},o=String(r.type??"");if(o==="action"&&String(r.value??"")==="cancel")return this.pendingQuestionByRequestId.delete(n),this.callbacks.sendLocalActionResult(e.action_id,"ok",{request_id:n,resolution:"cancel"}),{handled:!0,kind:"question_reply_cancel"};let a="";return o==="text"?a=String(r.value??""):o==="map"&&(a=(Array.isArray(r.entries)?r.entries:[]).map(c=>c.value).join(", ")),a?(this.pendingQuestionByRequestId.delete(n),this.pendingQuestionAnswerBySession.set(s.sessionId,a),this.callbacks.sendLocalActionResult(e.action_id,"ok",{request_id:n,resolution:"answer"}),h.info("cursor-adapter",`Question reply cached for next prompt: request_id=${n} session=${s.sessionId}`),{handled:!0,kind:"question_reply"}):(this.pendingQuestionByRequestId.delete(n),this.callbacks.sendLocalActionResult(e.action_id,"ok",{request_id:n,resolution:"cancel"}),{handled:!0,kind:"question_reply_empty"})}enqueueAssistantStreamDelta(e,t){t&&(e.assistantTextEmitted+=t,e.streamBuffer+=t,e.streamBuffer.length>=ve&&this.flushAssistantStreamBuffer(e))}flushAssistantStreamBuffer(e){if(!e.streamBuffer)return;const t=e.streamBuffer;e.streamBuffer="",e.seq+=1,this.callbacks.sendStreamChunk(e.event.event_id,e.event.session_id,t,e.seq,!1,void 0,void 0)}shouldExtendByLiveness(e){const t=this.activeBySession.get(e);if(!t||t.done)return!1;const n=t.child?.pid;if(!n||!T(n)||t.sawResult)return!1;const s=Date.now(),i=s-t.lastIoAt,r=this.transcriptFreshMsForSession(e);if(i<j||r!==null&&r<j)return t.livenessExtendStartAt=s,h.info("cursor-adapter",`Liveness check: fresh progress for ${t.event.event_id} (ioAge=${i}ms, transcriptAge=${r??"n/a"}, toolsInFlight=${t.toolCallsInFlight.size}), extending`),!0;const a=t.livenessExtendStartAt??s;return t.livenessExtendStartAt===void 0&&(t.livenessExtendStartAt=a),s-a>Q?(h.warn("cursor-adapter",`Liveness extension budget exhausted for ${t.event.event_id} (no progress for ${Math.round((s-a)/6e4)}min), allowing idle kill`),!1):(h.info("cursor-adapter",`Liveness check: turn in progress for ${t.event.event_id} (toolsInFlight=${t.toolCallsInFlight.size}, ioAge=${i}ms), extending`),!0)}transcriptFreshMsForSession(e){const t=this.sessionRuntime.get(e)?.cursorSessionId;if(!t)return null;let n=this.transcriptPathBySession.get(e);if(!n){const s=V(t);if(!s)return null;this.transcriptPathBySession.set(e,s),n=s}try{return Date.now()-N(n).mtimeMs}catch{return this.transcriptPathBySession.delete(e),null}}hasPendingQuestionForSession(e){const t=Date.now();for(const[n,s]of this.pendingQuestionByRequestId)if(s.sessionId===e){if(t-s.createdAt>x){this.pendingQuestionByRequestId.delete(n);continue}return!0}return!1}armActiveIdleTimer(e){const t=this.activeBySession.get(e);!t||t.done||(t.idleTimer&&clearTimeout(t.idleTimer),t.idleTimer=setTimeout(()=>{if(t.done)return;if(this.hasPendingQuestionForSession(e)){h.info("cursor-adapter",`Idle timeout skipped: awaiting AskQuestion for ${t.event.event_id}, resetting timer`),this.armActiveIdleTimer(e);return}if(this.shouldExtendByLiveness(e)){this.armActiveIdleTimer(e);return}if(t.sawResult){h.warn("cursor-adapter",`Idle after result with no close \u2014 finishing as responded: event=${t.event.event_id} session=${e}`),this.killChildWithFallback(t.child),this.finishActive(e,"responded");return}const n=t.child?.pid,i=!!(n&&T(n))?`liveness budget exhausted after ${Q/6e4}min`:"child process dead";h.error("cursor-adapter",`Idle timeout (${i}, toolsInFlight=${t.toolCallsInFlight.size}) \u2014 killing cursor child: event=${t.event.event_id} session=${e}`),this.killChildWithFallback(t.child),this.finishActive(e,"failed",`idle timeout: ${i}`)},ye),t.idleTimer.unref?.())}finishActive(e,t,n){const s=this.activeBySession.get(e);if(!s)return;s.done=!0,s.timer&&clearTimeout(s.timer),s.idleTimer&&clearTimeout(s.idleTimer),this.flushAssistantStreamBuffer(s),s.seq+=1;const i=String(s.event.quoted_message_id??"").trim()||void 0;this.callbacks.sendStreamChunk(s.event.event_id,s.event.session_id,"",s.seq,!0,void 0,i),h.info("cursor-adapter",`job finish: event=${s.event.event_id} session=${s.event.session_id} status=${t}${n?` msg=${n}`:""}`),this.snapshotAuditBoundary(s.event.event_id,e),this.emit("eventDone",s.event.event_id),this.callbacks.sendEventResult(s.event.event_id,t,n),t==="responded"?s.handle.emitDone({status:"completed"}):t==="canceled"?s.handle.emitDone({status:"canceled",error:n}):s.handle.emitDone({status:"failed",error:n}),this.activeBySession.delete(e),this.notifyBindingReady(e),this.tryStartNext(e)}snapshotAuditBoundary(e,t){const n=this.auditCaptures.get(e);if(!n)return;const s=this.sessionRuntime.get(t);this.completedAuditBoundaries.set(e,{adapterType:"cursor",providerSessionId:s?.cursorSessionId,cursorEvidence:{events:n}}),this.auditCaptures.delete(e)}shouldRetryWithoutContinue(e){if(!e.usedContinue||e.retryCount>0)return!1;const t=e.stderr.toLowerCase(),n=t.includes("resume")||t.includes("continue")||t.includes("chat")||t.includes("session"),s=t.includes("not found")||t.includes("no previous session")||t.includes("does not exist")||t.includes("invalid");return n&&s}ensureWorkspaceMcpAndSkills(e){if(!this.internalApi)return;this.ensureCursorMcpConfig(e,this.internalApi.url);const t=A(F(),".cursor","skills"),n=ue(t);n.length>0&&h.info("cursor-adapter",`Synced connector skills to ${t}: [${n.join(", ")}]`)}ensureCursorMcpConfig(e,t){const n=de(this.callbacks.getAgentId?.()),s={command:process.execPath,args:[se(ne(import.meta.url),"../../../mcp/acp-mcp-server.js"),"--api-url",t]},i=[e],r=this.resolveGitRoot(e);r&&r!==e&&i.push(r);let o=null;for(const a of i){const l=this.writeCursorMcpConfig(a,n,s,t);!o&&l&&(o=l)}o&&this.recordCursorMcpRegistry(e,o,s)}resolveGitRoot(e){try{return Z("git",["-C",e,"rev-parse","--show-toplevel"],{timeout:5e3,stdio:["ignore","pipe","ignore"],windowsHide:!0}).toString("utf8").trim()||null}catch{return null}}writeCursorMcpConfig(e,t,n,s){try{const i=A(e,".cursor"),r=A(i,"mcp.json");L(i,{recursive:!0});let o={};try{o=JSON.parse(P(r,"utf8"))}catch{o={}}(!o.mcpServers||typeof o.mcpServers!="object")&&(o.mcpServers={});const a=le(o.mcpServers,s);return o.mcpServers[t]=n,D(r,`${JSON.stringify(o,null,2)}
|
|
14
|
+
`,"utf8"),h.info("cursor-adapter",`MCP config synced: dir=${e} file=${r} server=${t}${a.length>0?` pruned=${a.join(",")}`:""}`),r}catch(i){return h.warn("cursor-adapter",`Failed to ensure ${A(e,".cursor","mcp.json")}: ${String(i)}`),null}}recordCursorMcpRegistry(e,t,n){try{const s=A(ie.data,"cursor"),i=A(s,"mcp-registry.json");L(s,{recursive:!0});let r={};try{r=JSON.parse(P(i,"utf8"))}catch{r={}}r[e]={mcp_path:t,server:n,updated_at:new Date().toISOString()},D(i,`${JSON.stringify(r,null,2)}
|
|
15
|
+
`,"utf8")}catch(s){h.warn("cursor-adapter",`Failed to record MCP registry: ${String(s)}`)}}}export{X as CURSOR_MODE_OPTIONS,$ as CursorAdapter,Ie as extractCursorAssistantStreamDelta,V as findCursorTranscript,ke as formatCursorCreatePlanText,J as isCursorAskQuestionTool,G as isCursorCreatePlanTool,we as mapCursorStreamJsonToRawEnvelope,be as parseCursorAskQuestionPayload};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{readFileSync as S,readdirSync as m}from"node:fs";import
|
|
1
|
+
import{readFileSync as S,readdirSync as m}from"node:fs";import a from"node:path";import{homedir as h}from"node:os";import{log as w}from"../../core/log/index.js";import{makeTitle as p,stripInjectedPreamble as y}from"../../core/util/session-title.js";import{openReadOnlyDatabase as F}from"../../core/util/sqlite-reader.js";function D(t=h()){return a.join(t,".cursor","chats")}function T(t){try{return JSON.parse(S(t,"utf8"))}catch{return null}}function b(t){const r=[];for(let e=0;e+34<=t.length;e+=1)t[e]===10&&t[e+1]===32&&r.push(t.subarray(e+2,e+34).toString("hex"));return r}function j(t){let r;try{r=JSON.parse(t.toString("utf8"))}catch{return}if(r.role!=="user")return;const e=r.content;if(typeof e=="string")return e;if(Array.isArray(e)){for(const n of e)if(n&&typeof n=="object"&&typeof n.text=="string")return n.text}}function x(t){const r=/<user_query>([\s\S]*?)<\/user_query>/.exec(t);if(r)return p(y(r[1]));const e=/\[system-identity\][\s\S]*?\[\/system-identity\]/.exec(t),n=/\[\[message_id:[^\]]+\]\]/.exec(t);if(!e&&!n)return;const o=y(t),i=o.search(/[\x00-\x08\x0b\x0c\x0e-\x1f]/),s=(i>=0?o.slice(0,i):o).replace(/^\uFFFD+/,"").trim();return p(s)}function A(t){if(t[0]!==10)return;let r=0,e=0,n=1;for(;n<t.length&&e<=28;){const o=t[n++];if(r|=(o&127)<<e,(o&128)===0)return r<0||n+r>t.length?void 0:t.subarray(n,n+r).toString("utf8");e+=7}}function g(t){const r=j(t);if(r){const n=x(r);if(n)return n}const e=A(t)??t.toString("utf8");if(!e.trimStart().startsWith("{"))return x(e)}function C(t){const r=F(t);if(r)try{const e=r.all("SELECT value FROM meta LIMIT 1");if(e.length===0)return;const n=JSON.parse(Buffer.from(e[0].value,"hex").toString("utf8")),o=new Map;for(const i of r.all("SELECT id, data FROM blobs"))o.set(i.id,Buffer.from(i.data));if(n.latestRootBlobId){const i=o.get(n.latestRootBlobId);if(i)for(const s of b(i)){const c=o.get(s);if(!c)continue;const u=g(c);if(u)return u}}for(const i of o.values()){const s=g(i);if(s)return s}return}catch{return}finally{r.close()}}function k(t=h()){const r=D(t),e=[];let n;try{n=m(r,{withFileTypes:!0})}catch{return e}for(const o of n){if(!o.isDirectory())continue;const i=a.join(r,o.name);let s;try{s=m(i,{withFileTypes:!0})}catch{continue}for(const c of s){if(!c.isDirectory())continue;const u=a.join(i,c.name),d=a.join(u,"meta.json"),f=T(d);if(!f?.updatedAtMs)continue;const l=C(a.join(u,"store.db"));!f.hasConversation&&!l||e.push({sessionId:c.name,cwd:f.cwd??"",title:l,createdAt:f.createdAtMs,updatedAt:f.updatedAtMs,filePath:d})}}return e.sort((o,i)=>i.updatedAt-o.updatedAt),w.info("cursor-session-scanner",`Scanned ${e.length} Cursor sessions from ${r}`),e}export{D as resolveCursorChatsDir,k as scanCursorSessions};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{createHmac as $,randomUUID as C}from"node:crypto";import{EventEmitter as M}from"node:events";import{readFileSync as z}from"node:fs";import{createConnection as G}from"node:net";import{isDshBridgedGrixTool as U}from"./grix-tool-bridge.js";import{DshBridgeJsonRpcPeer as L,GRIX_DSH_PROTOCOL_MAX as N,GRIX_DSH_PROTOCOL_MIN as B}from"./profile-bridge-protocol.js";import{resolveDshProfileIdentity as F,selectDshBridgeEndpoint as K}from"./profile-resolver.js";import{DEFAULT_DSH_PRESETS as V,normalizeDshPresets as X,normalizeDshProviders as J,resolveDshAgentPreset as W,resolveDshProviderId as Z}from"./toolbar-state.js";import{effectiveDshReasoningEffort as Q}from"./types.js";const Y=C(),R=5e3,y=1e4,tt=6e4;class et extends M{peer;sessionId;ownerId;onCursorAck;notificationsActive=!1;pendingNotifications=[];constructor(t,e,c,s){super(),this.peer=t,this.sessionId=e,this.ownerId=c,this.onCursorAck=s,t.on("notification",(o,a)=>{if(!this.notificationsActive){this.pendingNotifications.push({method:o,params:a});return}this.deliverNotification(o,a)}),t.on("protocolError",o=>this.emit("protocolError",o))}prompt(t,e){return this.peer.call("session/prompt",{sessionId:t.sessionId,ownerId:this.ownerId,contentBlocks:t.contentBlocks},e)}async shutdown(t){try{await this.peer.call("session/close",{sessionId:this.sessionId,ownerId:this.ownerId},
|
|
1
|
+
import{createHmac as $,randomUUID as C}from"node:crypto";import{EventEmitter as M}from"node:events";import{readFileSync as z}from"node:fs";import{createConnection as G}from"node:net";import{isDshBridgedGrixTool as U}from"./grix-tool-bridge.js";import{DshBridgeJsonRpcPeer as L,GRIX_DSH_PROTOCOL_MAX as N,GRIX_DSH_PROTOCOL_MIN as B}from"./profile-bridge-protocol.js";import{resolveDshProfileIdentity as F,selectDshBridgeEndpoint as K}from"./profile-resolver.js";import{DEFAULT_DSH_PRESETS as V,normalizeDshPresets as X,normalizeDshProviders as J,resolveDshAgentPreset as W,resolveDshProviderId as Z}from"./toolbar-state.js";import{effectiveDshReasoningEffort as Q}from"./types.js";const Y=C(),R=5e3,y=1e4,tt=6e4;class et extends M{peer;sessionId;ownerId;onCursorAck;notificationsActive=!1;pendingNotifications=[];constructor(t,e,c,s){super(),this.peer=t,this.sessionId=e,this.ownerId=c,this.onCursorAck=s,t.on("notification",(o,a)=>{if(!this.notificationsActive){this.pendingNotifications.push({method:o,params:a});return}this.deliverNotification(o,a)}),t.on("protocolError",o=>this.emit("protocolError",o))}prompt(t,e){return this.peer.call("session/prompt",{sessionId:t.sessionId,ownerId:this.ownerId,contentBlocks:t.contentBlocks},e)}async shutdown(t){try{await this.peer.call("session/close",{sessionId:this.sessionId,ownerId:this.ownerId},g(t,5e3))}finally{this.peer.close()}}close(t){this.peer.close(t)}activateNotifications(){if(!this.notificationsActive){this.notificationsActive=!0;for(const t of this.pendingNotifications.splice(0))this.deliverNotification(t.method,t.params)}}call(t,e,c){const s=e&&typeof e=="object"?e:{};return this.peer.call(t,{sessionId:this.sessionId,ownerId:this.ownerId,...s},g(c,1e4))}async ack(t,e){if(!Number.isSafeInteger(t)||t<=0)throw Object.assign(new Error("Bridge ACK cursor must be a positive integer"),{code:"invalid_params"});const c=await this.peer.call("session/ack",{sessionId:this.sessionId,ownerId:this.ownerId,cursor:t},g(e,5e3));return await this.onCursorAck?.(t),c}cancel(t){return this.peer.call("session/cancel",{sessionId:this.sessionId,ownerId:this.ownerId},g(t,3e4))}setOptions(t,e){return this.peer.call("session/setOptions",{sessionId:this.sessionId,ownerId:this.ownerId,model:t.modelId,modeId:t.modeId},g(e,1e4))}deliverNotification(t,e){this.emit("notification",t,e)}}class mt{options;client=null;peer=null;socket=null;exitEmitter=new M;intentionalClose=!1;constructor(t){this.options=t}async start(){const t=F({dshHome:this.options.dshHome,profileName:this.options.profileName}),e=K(t);if(e.protocolMax<B||e.protocolMin>N)throw Object.assign(new Error(`Incompatible Grix DSH Bridge protocol ${e.protocolMin}-${e.protocolMax}`),{code:"bridge_incompatible"});const c=z(e.tokenPath,"utf8").trim();if(!c)throw Object.assign(new Error("DSH Bridge token file is empty"),{code:"auth_failed"});const s=await it(e.address,this.options.connectTimeoutMs??R);this.socket=s;const o=new L(s,{maxFrameBytes:this.options.maxFrameBytes,capture:this.options.capture});this.peer=o,o.on("close",()=>{this.socket=null,this.peer=null,this.client=null,this.intentionalClose||this.exitEmitter.emit("exit")}),o.setRequestHandler(async(i,l)=>{const n=l&&typeof l=="object"?l:{};if(i==="interaction/requestApproval")return{outcome:await this.options.onApproval?.(n)??"unavailable"};if(i==="grix/tool-call"){if(!this.options.onToolInvoke)throw Object.assign(new Error("grix tools are not wired for this Connector session"),{code:"capability_unavailable"});const p=String(n.tool??"").trim(),S=n.args&&typeof n.args=="object"?n.args:{};if(!p)throw Object.assign(new Error("grix/tool-call requires a tool name"),{code:"invalid_params"});if(!U(p))throw Object.assign(new Error(`grix tool "${p}" is not exposed to DSH`),{code:"capability_unavailable"});const q=Number(n.timeoutMs)||void 0;return{ok:!0,value:await this.options.onToolInvoke(p,S,q)}}throw Object.assign(new Error(`Unsupported Bridge request: ${i}`),{code:"capability_unavailable"})});const a=C(),I=$("sha256",c).update(`${a}
|
|
2
2
|
${t.profileKey}
|
|
3
|
-
${e.instanceId}`).digest("hex"),
|
|
3
|
+
${e.instanceId}`).digest("hex"),w=AbortSignal.timeout(this.options.initializeTimeoutMs??this.options.connectTimeoutMs??R),r=await o.call("bridge/initialize",{protocol:{min:B,max:N},client:{name:"grix-connector",version:process.env.npm_package_version??"unknown",instanceId:Y},profileKey:t.profileKey,requestedRole:"controller",auth:{nonce:a,proof:I}},w),j=r.profile&&typeof r.profile=="object"?r.profile:{},E=String(j.dshVersion??"");if(E!==e.dshVersion)throw o.close(new Error("DSH Profile version changed between discovery and handshake")),Object.assign(new Error(`DSH Profile version mismatch: discovery=${e.dshVersion||"unknown"} handshake=${E||"unknown"}`),{code:"bridge_incompatible"});const H=r.capabilities&&typeof r.capabilities=="object"?r.capabilities:{},_=this.options.settings.skillIds??[],P=(this.options.settings.skillIdsExplicit===!0||_.length>0)&&H.sessionSkillAllowlist===!0,d=String(this.options.settings.providerId??"").trim(),v=this.options.managedProvider,b=!!v&&(!d||d===v.id);let u=[],x=!1;const A=b?0:this.options.catalogRefreshAttempts??4;for(let i=0;i<=A;i++){try{const l=AbortSignal.timeout(this.options.catalogTimeoutMs??y),n=await o.call("profile/listProviders",{},l),p=J(n.providers);if(x=!0,p.length>0&&(u=p),!d||u.some(S=>S.id===d)||b)break}catch{break}i<A&&await new Promise(l=>setTimeout(l,150))}const m=b&&v?v.id:x&&d&&!u.some(i=>i.id===d)?d:Z(this.options.settings.providerId,u);let f;try{const i=AbortSignal.timeout(this.options.catalogTimeoutMs??y),l=await o.call("profile/listModels",{provider:m},i);f=Array.isArray(l.models)?l.models.filter(n=>!!n&&typeof n=="object"):[]}catch{}const D=(f??[]).some(i=>String(i.id??"")===this.options.settings.modelId)?this.options.settings.modelId:String(f?.[0]?.id??this.options.settings.modelId);let k=[...V];try{const i=AbortSignal.timeout(this.options.catalogTimeoutMs??y),l=await o.call("profile/listPresets",{},i),n=X(l.presets);n.length>0&&(k=n)}catch{}const T=W(this.options.settings.agentPreset,k);this.client=new et(o,this.options.sessionId,this.options.ownerId,this.options.onCursorAck);try{const i=AbortSignal.timeout(this.options.sessionCreateTimeoutMs??tt);await o.call("session/create",{sessionId:this.options.sessionId,ownerId:this.options.ownerId,cwd:this.options.cwd,provider:m,model:D,reasoningEffort:Q(this.options.settings),...m==="codex-app-server"?{}:{maxTokens:this.options.settings.maxTokens},modeId:this.options.settings.modeId,agentPreset:T,pluginIds:this.options.settings.pluginIds??[],...P?{skillIds:_,skillIdsExplicit:!0}:{},...P&&this.options.skillsDir?{skillsDir:this.options.skillsDir}:{},...this.options.pluginOverlayPath?{pluginOverlayPath:this.options.pluginOverlayPath}:{},systemPrompt:this.options.systemPrompt,resume:this.options.resumeSession===!0,lastAckCursor:this.options.lastAckCursor??0,...this.options.managedProvider&&m===this.options.managedProvider.id?{managedProvider:this.options.managedProvider}:{}},i)}catch(i){throw this.client.close(i instanceof Error?i:new Error(String(i))),this.client=null,i}const O=r.serverInfo&&typeof r.serverInfo=="object"?r.serverInfo:{};return{serverInfo:{name:String(O.name??"grix-dsh-profile-bridge"),version:String(O.version??"unknown")},profile:r.profile,capabilities:r.capabilities,...f!==void 0?{models:f}:{},selectedModel:D,providers:u,selectedProvider:m,presets:k,selectedPreset:T}}async shutdown(){this.intentionalClose=!0;try{await this.client?.shutdown()}finally{this.peer?.close(),this.client=null,this.peer=null,this.socket=null}}async terminate(){this.intentionalClose=!0,this.peer?.close(new Error("DSH Profile Bridge connection terminated")),this.client=null,this.peer=null,this.socket=null}async cancel(t){await this.client?.cancel()}async hasBackgroundWork(){return!1}isConnected(){return!!this.client&&!!this.peer?.isOpen}onExit(t){return this.exitEmitter.on("exit",t),()=>this.exitEmitter.off("exit",t)}}function g(h,t){return h??AbortSignal.timeout(t)}function it(h,t){return new Promise((e,c)=>{const s=G(h),o=setTimeout(()=>{a(),s.destroy(),c(Object.assign(new Error("Timed out connecting to DSH Profile Bridge"),{code:"bridge_connect_timeout"}))},t);o.unref?.();const a=()=>{clearTimeout(o),s.off("connect",I),s.off("error",w)},I=()=>{a(),s.setNoDelay(!0),e(s)},w=r=>{a(),c(Object.assign(r,{code:"bridge_connect_failed"}))};s.once("connect",I),s.once("error",w)})}export{y as BRIDGE_CATALOG_TIMEOUT_MS,R as BRIDGE_INITIALIZE_TIMEOUT_MS,tt as BRIDGE_SESSION_CREATE_TIMEOUT_MS,mt as DshProfileRuntime,et as DshProfileRuntimeClient};
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import{EventEmitter as T}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 T{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,P=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"},b=[{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 T{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
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:b.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:b.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 ${P/1e3}s`)))},P);this.process.stdout?.on("data",l=>{if(u+=l.toString(),!p)for(const g of u.split(`
|
|
3
3
|
`)){const _=g.match(/opencode server listening on (https?:\/\/[^\s]+)/);if(_){clearTimeout(I),p=!0,this.alive=!0,i(_[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"}}async handleExecApprovalEvent(e){if(!this.activeRun)return{delivered:!1,errorCode:"approval_not_found",errorMsg:"no active run"};const s=String(e.approval_id??e.approval_command_id??e.tool_call_id??"");let t=s?this.pendingPermissions.get(s):void 0;if(!t){for(const n of this.pendingPermissions.values())if(n.eventId===this.activeRun.eventId){t=n;break}}if(!t)return{delivered:!1,errorCode:"approval_not_found",errorMsg:"That approval request is no longer pending."};const i=e.decision==="deny"?"reject":e.decision==="allow-always"?"always":"once";return clearTimeout(t.timer),this.pendingPermissions.delete(t.permissionId),this.transport.respondPermission(t.ocSessionId,t.permissionId,i,t.cwd).then(()=>{r.info("opencode-adapter",`Permission ${i} (text event): ${t.permissionId}`),this.resetIdleTimer()}).catch(n=>{r.warn("opencode-adapter",`Permission response failed: ${n}`),this.resetIdleTimer()}),{delivered:!0}}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;
|
|
5
|
-
`)
|
|
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;if(e.contextMessages&&e.contextMessages.length>0){const t=e.contextMessages.map(i=>`[context] ${i.senderId}: ${i.content}`).join(`
|
|
5
|
+
`);s=`${s}
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
`)
|
|
7
|
+
[Conversation context]
|
|
8
|
+
${t}`}return 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);if(Array.isArray(i)&&i.length>0){const n=i.map(o=>`[context] ${o.sender_id??"unknown"}: ${o.content}`).join(`
|
|
9
|
+
`);s=`${s}
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
[Conversation context]
|
|
12
|
+
${n}`}}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,10 @@
|
|
|
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 w}from"node:url";import{randomUUID as S}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),
|
|
2
|
-
`);i("sqlite3",[s,c],{timeout:1e4,stdio:"ignore",windowsHide:!0}),n.info("openhuman-adapter",`MCP server registered in SQLite: ${s}`);const l=p(u(),".openhuman","skills"),
|
|
3
|
-
`)
|
|
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 w}from"node:url";import{randomUUID as S}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),a=`http://${t}:${e}`;if(await this.transport.connect(a,i),this.transport.on("event",r=>this.handleSocketEvent(r)),this.options.sessionToken)try{await this.transport.storeSession({token:this.options.sessionToken}),n.info("openhuman-adapter","Session token stored")}catch(r){n.warn("openhuman-adapter",`Failed to store session token: ${r}`)}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 a=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,a).then(r=>{this.activeRun&&this.activeRun.eventId===e&&(this.activeRun.requestId=r.request_id,r.accepted||(n.warn("openhuman-adapter",`web_chat not accepted: ${r.request_id}`),this.finishRun("failed","Chat request not accepted")))}).catch(r=>{n.error("openhuman-adapter",`web_chat failed: ${r}`),this.finishRun("failed",String(r))})}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(w(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"),a=S(),r=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 ('${a}', '${t.name}', 'Grix Connector Tools', 'Grix platform query and management tools', 'node', '${t.command}', '${r.replace(/'/g,"''")}', ${Date.now()});`].join(`
|
|
2
|
+
`);i("sqlite3",[s,c],{timeout:1e4,stdio:"ignore",windowsHide:!0}),n.info("openhuman-adapter",`MCP server registered in SQLite: ${s}`);const l=p(u(),".openhuman","skills"),o=C(l);o.length>0&&n.info("openhuman-adapter",`Synced connector skills to ${l}: [${o.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),a=this.config.args??[],h=a.includes("run")||a.includes("serve")?a:["run","--host",t,"--port",String(e),...a],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(o){throw String(o?.code??"")==="ENOENT"?new Error(`Bound directory does not exist: ${l}. Please rebind with /grix open <valid-directory>.`):o}try{this.process=b(i,h,{env:c,cwd:l}).process}catch(o){throw n.error("openhuman-adapter",`Spawn threw: ${o}`),this.alive=!1,o}this.process.on("error",o=>{n.error("openhuman-adapter",`Spawn error: ${o.message}`),this.alive=!1,this.transport.disconnect(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed",`Spawn error: ${o.message}`),this.clearRun()),this.stopped||this.emit("exit",1)}),this.process.on("exit",o=>{n.info("openhuman-adapter",`Process exited (code=${o})`),this.alive=!1,this.transport.disconnect(),this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed",`Process exited (code=${o})`),this.clearRun()),this.stopped||this.emit("exit",o??1)}),this.process.stderr?.on("data",o=>{const d=o.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(a=>setTimeout(a,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 r=this.completedEvents.values();for(let c=0;c<500;c++)r.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,a=s.clientMsgId;t==="failed"&&e&&this.callbacks.sendRunError(s.eventId,s.sessionId,e),this.callbacks.sendFinalStreamChunkReliable?this.callbacks.sendFinalStreamChunkReliable(s.eventId,s.sessionId,i,a).then(()=>{this.callbacks.sendEventResult(s.eventId,t,e)}).catch(r=>{const h=t==="responded"?"failed":t,c=t==="responded"?`final output delivery failed: ${r instanceof Error?r.message:String(r)}`:e;this.callbacks.sendEventResult(s.eventId,h,c)}):(this.callbacks.sendStreamChunk(s.eventId,s.sessionId,"",i,!0,a),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;if(t.contextMessages&&t.contextMessages.length>0){const s=t.contextMessages.map(i=>`[context] ${i.senderId}: ${i.content}`).join(`
|
|
3
|
+
`);e=`${e}
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
`)
|
|
5
|
+
[Conversation context]
|
|
6
|
+
${s}`}return 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);if(Array.isArray(i)&&i.length>0){const a=i.map(r=>`[context] ${r.sender_id??"unknown"}: ${r.content}`).join(`
|
|
7
|
+
`);e=`${e}
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
[Conversation context]
|
|
10
|
+
${a}`}}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};
|