grix-connector 4.0.0 → 4.0.2
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 +9 -9
- package/dist/adapter/agy/agy-adapter.js +7 -7
- package/dist/assets/dsh-bridge/{grix-dsh-bridge-4.0.0.tgz → grix-dsh-bridge-4.0.2.tgz} +0 -0
- package/dist/assets/dsh-bridge/manifest.json +5 -5
- package/dist/core/mcp/internal-api-server.js +1 -1
- package/dist/grix.js +0 -0
- package/dist/kimi-plugins/grix-layer/references/gateway-architecture.md +5 -1
- package/dist/kimi-plugins/grix-layer/scripts/http-facade.mjs +16 -0
- package/dist/kimi-plugins/grix-layer/scripts/lib/tool-server.mjs +74 -59
- package/dist/kimi-plugins/index.js +2 -3
- package/dist/protocol/acp-client.js +1 -1
- package/dist/protocol/acp-terminal.js +2 -0
- package/package.json +1 -1
- package/dist/adapter/claude/claude-bridge-server.js +0 -1
- package/dist/adapter/claude/claude-tools.js +0 -1
- package/dist/adapter/claude/claude-worker-client.js +0 -1
- package/dist/adapter/claude/mcp-http-launcher.js +0 -2
- package/dist/adapter/claude/result-timeout.js +0 -1
- package/dist/adapter/deepseek/deepseek-adapter.js +0 -6
- package/dist/adapter/deepseek/index.js +0 -1
- package/dist/adapter/qwen/index.js +0 -1
- package/dist/adapter/qwen/qwen-adapter.js +0 -4
- package/dist/aibot/client.js +0 -1
- package/dist/aibot/index.js +0 -1
- package/dist/aibot/types.js +0 -0
- package/dist/core/access/allowlist-gate.js +0 -1
- package/dist/core/access/allowlist-store.js +0 -1
- package/dist/core/access/index.js +0 -1
- package/dist/core/file-ops/handler.js +0 -1
- package/dist/core/file-ops/list-files.js +0 -1
- package/dist/core/file-ops/types.js +0 -0
- package/dist/log.js +0 -3
- package/dist/main.js +0 -31
- package/dist/mcp/stream-http/config.js +0 -1
- package/dist/mcp/stream-http/connection-binding.js +0 -1
- package/dist/mcp/stream-http/event-tool-executor.js +0 -1
- package/dist/mcp/stream-http/gateway.js +0 -1
- package/dist/mcp/stream-http/index.js +0 -1
- package/dist/mcp/stream-http/security.js +0 -1
- package/dist/mcp/stream-http/session-manager.js +0 -1
- package/dist/mcp/stream-http/tool-executor.js +0 -1
- package/dist/mcp/stream-http/tool-registry.js +0 -1
- package/dist/mcp/stream-http/tool-schemas.js +0 -1
- package/dist/session/index.js +0 -1
- package/dist/session/manager.js +0 -1
- package/dist/transport/index.js +0 -1
- package/dist/transport/json-rpc.js +0 -3
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import{fileURLToPath as H}from"node:url";import
|
|
2
|
-
`)}function de(c,e,t){const i=c&&typeof c=="object"?c:{},s=i.toolCall&&typeof i.toolCall=="object"?i.toolCall:{},n=[s.rawInput,s.raw_input,s.input,s.arguments,s.args,q(s.content),s.content,s.description,i.tool_input,i.toolInput,e];for(const o of n){const a=M(o);if(a&&a!==t)return a}return""}function ue(c){if(!`${c.toolName} ${c.toolTitle}`.toLowerCase().replace(/[^a-z]/g,"").includes("askuserquestion"))return null;const t=c.options.filter(o=>/^q\d+_opt_\d+$/.test(o.optionId));if(t.length===0)return null;const i=c.rawParams&&typeof c.rawParams=="object"?c.rawParams:{},s=i.toolCall&&typeof i.toolCall=="object"?i.toolCall:{},n=q(s.content).trim();return n?{prompt:n,answerOptions:t}:null}import{scanSkills as O}from"../claude/skill-scanner.js";import{checkKiroSessionActivity as he,checkKimiSessionActivity as pe,hasRunningKimiDetachedTask as me}from"./session-activity.js";import{resolveCliPath as fe,getCliVersion as ge}from"../../core/util/cli-probe.js";const w=h.dirname(H(import.meta.url)),B=[{modelId:"deepseek-v4-pro",name:"DeepSeek V4 Pro"},{modelId:"deepseek-v4-flash",name:"DeepSeek V4 Flash"}],R=[{modelId:"kimi-code/kimi-for-coding",name:"K2.7 Coding"},{modelId:"kimi-code/kimi-for-coding-highspeed",name:"K2.7 Coding Highspeed"},{modelId:"kimi-code/k3",name:"K3"}];function k(c,e){const t=Number(process.env[c]);return Number.isFinite(t)&&t>0?t:e}const ve=200,Ie=60*1e3,ye=600*1e3,T=k("GRIX_ACP_TURN_INACTIVITY_TIMEOUT_MS",900*1e3),D=2,F=5e3,j=80,U=1200*1e3,Ce=k("GRIX_ACP_BUSY_RETRY_INTERVAL_MS",5e3),L=k("GRIX_ACP_BUSY_WAIT_MAX_MS",1800*1e3),Ae=6e4,Se=2e3,Te=12e3,K=3e3,W=3e4,_e=["[grix protocol] Markers like [[message_id:<id>]] and [[quoted_message_id:<id>]] are metadata, not user prose \u2014 never repeat them verbatim except as instructed here.","The message you are replying to carries its id in a [[message_id:<id>]] marker.","If (and only if) you want your reply to quote/thread onto that specific message, include [[quoted_message_id:<id>]] anywhere in your reply using that id; otherwise omit it."].join(" ");function be(c){return!(!(c instanceof Error)||c.code!==-32603||se(c))}function we(c){if(!(c instanceof Error))return!1;const e=c.data;return e&&typeof e=="object"&&e.code==="turn.agent_busy"?!0:/cannot launch a new turn/i.test(c.message)}function I(c){if(!(c instanceof Error))return String(c);let e=c.message;const t=c.data;return t&&(e+=` data=${JSON.stringify(t)}`),e}function Re(c){return c.replace(/\u001b\[[0-9;?]*[ -/]*[@-~]/g,"")}class it extends ${type="acp";config;callbacks;agentProcess=null;acpClient=null;internalApi=null;activeRun=null;compacting=!1;compactingTimer=null;compactionDoneResolver=null;pendingAutoCompact=!1;eventResults=null;pendingApprovals=new Map;pendingQuestions=new Map;clientMsgSeq=0;stopped=!1;acpAuthMethod;acpInitialMode;acpInitialModel;acpMcpTools;terminalAuthChild=null;rawTransport;approvalMode;autoInjectArgs;nativeProviderScope;reasonixAuditTranscriptPath;auditAgentType;trimToolWirePayload;activeAuditEventId=null;auditCaptures=new Map;completedAuditBoundaries=new Map;bindingStore;sessionBindings=new Map;deferredEvents=new Map;currentAibotSessionId;bridgeLog;sessionConnected=!1;sessionContextPending=!1;baselineGroupPids=[];rawEventSeq=0;recoveryContextBySessionId=new Map;quoteProtocolTaughtSessions=new Set;identityInjectedSessions=new Set;orphanBuffer="";orphanQuotedStream=new C;orphanFlushTimer=null;orphanSessionId=null;orphanSuppressedUntil=0;constructor(e,t,i){super(),this.config=e,this.callbacks=t,this.bridgeLog=i?.bridgeLog??null,this.acpAuthMethod=i?.acpAuthMethod,this.acpInitialMode=i?.acpInitialMode,this.acpInitialModel=i?.acpInitialModel,this.acpMcpTools=i?.acpMcpTools??!0,this.rawTransport=i?.rawTransport??!1,this.approvalMode=i?.approvalMode??"default",this.autoInjectArgs=i?.autoInjectArgs,this.nativeProviderScope=i?.nativeProviderScope,this.reasonixAuditTranscriptPath=i?.reasonixAuditTranscriptPath,this.auditAgentType=String(i?.agentType??h.basename(e.command)).trim().toLowerCase();const s=h.basename(e.command).toLowerCase(),n=this.auditAgentType==="copilot"||/^copilot(?:\.cmd)?$/i.test(s)||/^gh(?:\.exe)?$/i.test(s)&&(e.args??[]).includes("copilot");if(this.trimToolWirePayload=["kimi","kiro","qwen","reasonix","copilot"].includes(this.auditAgentType)||s.includes("kimi")||s==="kiro-cli"||/^qwen(?:\.cmd)?$/i.test(s)||s.includes("reasonix")||n,this.bindingStore=i?.bindingStore??null,this.currentAibotSessionId=i?.aibotSessionId?String(i.aibotSessionId).trim():void 0,i?.eventResultsPath&&(this.eventResults=new oe(i.eventResultsPath)),this.bindingStore&&this.currentAibotSessionId){const o=this.bindingStore.get(this.currentAibotSessionId);o?.cwd&&this.sessionBindings.set(this.currentAibotSessionId,o.cwd)}}async start(){if(await this.spawnProcess(),!this.bindingStore){await this.connectSession(this.resolveCwd());return}const e=this.currentAibotSessionId?this.sessionBindings.get(this.currentAibotSessionId):void 0;e&&await this.connectSession(e)}async stop(){this.stopped=!0,this.rejectDeferredEvents("adapter stopped"),this.cancelAllDeferredTimers(),this.pendingAutoCompact=!1,this.compacting=!1,this.compactingTimer&&(clearTimeout(this.compactingTimer),this.compactingTimer=null),this.compactionDoneResolver&&(this.compactionDoneResolver(),this.compactionDoneResolver=null),this.activeRun&&this.finishRun("canceled","adapter stopped"),this.flushOrphanBuffer(),this.acpClient&&(this.acpClient.clearSettleTimer(),this.acpClient.removeAllListeners(),this.acpClient=null),this.agentProcess&&(await this.agentProcess.close(),this.agentProcess=null),this.baselineGroupPids=[],this.internalApi&&(await this.internalApi.stop(),this.internalApi=null),this.terminalAuthChild&&(this.terminalAuthChild.kill("SIGKILL"),this.terminalAuthChild=null)}isAlive(){return this.agentProcess?.alive??!1}async createSession(e){return this.acpClient?.sessionId??""}async resumeSession(e,t){}async destroySession(e){this.quoteProtocolTaughtSessions.delete(e),this.identityInjectedSessions.delete(e)}onAgentProfileChanged(){this.identityInjectedSessions.size>0&&(r.info("acp-adapter",`agent profile changed; clearing ${this.identityInjectedSessions.size} identity-injected session(s)`),this.identityInjectedSessions.clear())}sendPrompt(e){const t=new ke(e.adapterSessionId);return this.acpClient?.isAlive&&this.acpClient.send(e.text).catch(i=>{t.emitError(i instanceof Error?i:new Error(String(i)))}),t}async cancel(e){this.activeRun&&this.acpClient&&(await this.acpClient.cancel(),this.flushStream(),this.finishRun("canceled","stopped by user"),this.suppressOrphanOutput(W))}setPermissionHandler(e){}async ping(e){return this.acpClient?.isAlive?this.acpClient.ping(e):this.agentProcess?.alive??!1}getStatus(){return{alive:this.agentProcess?.alive??!1,busy:this.activeRun!==null||this.compacting,sessions:this.sessionConnected?1:0}}getActiveEventIds(){return this.activeRun?[this.activeRun.eventId]:[]}clearActiveEventForShutdown(){this.activeRun&&(this.snapshotAuditBoundary(this.activeRun.eventId),this.clearRunTimers(this.activeRun),this.activeRun=null)}takeAuditBoundary(e){const t=this.completedAuditBoundaries.get(e);return t&&this.completedAuditBoundaries.delete(e),t}clearRunTimers(e){e.flushTimer&&(clearTimeout(e.flushTimer),e.flushTimer=null),e.idleTimer&&(clearTimeout(e.idleTimer),e.idleTimer=null),e.busyRetryTimer&&(clearTimeout(e.busyRetryTimer),e.busyRetryTimer=null)}getMcpConfig(){if(!this.internalApi)return null;this.internalApi.setMcpBridgeUpHandler(t=>this.onMcpBridgeUp(t));const e=h.resolve(w,"../../mcp/mcp-bridge-server.js");return{name:"grix-app-bridge",command:process.execPath,args:[e,"--ws-url",this.internalApi.mcpBridgeWsUrl]}}async hasBackgroundWork(){const e=this.agentProcess?.pid;return e?Q(e,[e,...this.baselineGroupPids]):!1}captureBaselineGroupPids(){const e=this.agentProcess?.pid;e&&z(e).then(t=>{this.baselineGroupPids=t}).catch(()=>{})}async probe(e){const t=this.config.command||"gemini",i=await fe(t),s=i!==null;let n=null,o;if(s){const p=await ge(t);n=p.version,p.error&&(o=p.error)}else o={code:"cli_not_found",message:`command not found: ${t}`};const a=this.acpClient?.model??null,l=this.getStatus();let d;return e?.conversation?this.acpClient?.isAlive?d={attempted:!0,ok:!0,latency_ms:null}:d={attempted:!0,ok:!1,latency_ms:null,error:{code:"unsupported",message:"ACP session not connected, cannot probe conversation"}}:d={attempted:!1,ok:!1,latency_ms:null},{cli:{command:t,installed:s,path:i,version:n,...o?{error:o}:{}},conversation:d,config:{model:a,base_url:null,source:{model:a?"runtime":"unknown",base_url:"unknown"}},process:{started:!!this.agentProcess,alive:l.alive,busy:l.busy}}}getSupportedCommands(){return this.acpClient?.availableCommands?.length?this.acpClient.availableCommands.map(e=>({name:e.name,description:e.description??"",args:e.args})):[{name:"model",description:"List or set model",args:"[model_id]"},{name:"mode",description:"List or set collaboration mode",args:"[mode_id]"},{name:"interrupt",description:"Interrupt current run"},{name:"compact",description:"\u538B\u7F29\u4E0A\u4E0B\u6587"},{name:"status",description:"Show session status"},{name:"skills",description:"List available skills"}]}async execCommand(e,t,i){return this.acpClient?.supportsCommandsExecute?this.execCommandViaAcp(e,t):this.execCommandFallback(e,t)}async execCommandViaAcp(e,t){if(!this.acpClient)return{status:"failed",message:"No active ACP session"};if(e==="interrupt")return this.activeRun?(await this.cancel(this.activeRun.sessionId),{status:"ok",message:"Run interrupted"}):{status:"failed",message:"No active run to interrupt"};if(e==="compact"){if(this.compacting)return{status:"failed",message:"Compaction already in progress"};if(this.activeRun)return{status:"failed",message:"agent busy"};this.compacting=!0;try{const i=await this.acpClient.executeCommand(e,t||void 0);return this.finishCompaction("acp-commands-execute"),{status:i.status==="failed"?"failed":"ok",message:i.message??"Compacted",data:i.data}}catch(i){return this.finishCompaction("error"),i?.code===-32601?this.execCommandFallback(e,t):{status:"failed",message:`compact failed: ${i instanceof Error?i.message:i}`}}}try{const i=await this.acpClient.executeCommand(e,t||void 0);return{status:i.status??"ok",message:i.message,data:i.options?{command:e,options:i.options}:i.data}}catch(i){return i?.code===-32601?this.execCommandFallback(e,t):{status:"failed",message:`Command failed: ${i instanceof Error?i.message:i}`}}}async execCommandFallback(e,t){try{switch(e){case"model":{const i=t.trim();if(i)return await this.setModel(i)?{status:"ok",message:`Model set to ${i}`}:{status:"failed",message:`Failed to set model: ${i}`};const s=this.buildToolbarContext("model_list");return{status:"ok",message:`Current: ${s.currentModelId||"unknown"}`,data:s}}case"mode":{const i=t.trim();if(i)return await this.setMode(i)?{status:"ok",message:`Mode set to ${i}`}:{status:"failed",message:`Failed to set mode: ${i}`};const s=this.buildToolbarContext("mode_list");return{status:"ok",message:`Current: ${s.currentModeId||"unknown"}`,data:s}}case"interrupt":return this.activeRun?this.acpClient?(await this.cancel(this.activeRun.sessionId),{status:"ok",message:"Run interrupted"}):{status:"failed",message:"Not connected"}:{status:"failed",message:"No active run to interrupt"};case"compact":{if(!this.acpClient)return{status:"failed",message:"No active ACP session"};if(this.compacting)return{status:"failed",message:"Compaction already in progress"};if(this.activeRun)return{status:"failed",message:"agent busy"};this.compacting=!0;const i=new Promise(s=>{this.compactionDoneResolver=s});try{await this.acpClient.send("/compact")}catch(s){throw this.finishCompaction("send-failed"),s}return this.compactingTimer=setTimeout(()=>this.finishCompaction("timeout"),15e3),this.compactingTimer.unref?.(),await i,{status:"ok",message:"Compacted"}}case"status":{const i=this.getStatus(),s=this.acpClient?.sessionOptions;return{status:"ok",message:`Alive: ${i.alive}, Busy: ${i.busy}, Model: ${s?.currentModelId??"unknown"}, Mode: ${s?.currentModeId??"unknown"}`,data:{alive:i.alive,busy:i.busy,sessions:i.sessions,model:s?.currentModelId??"",mode:s?.currentModeId??""}}}case"skills":{const i=O({mode:this.config.command==="kiro-cli"?"kiro":"gemini",projectDir:process.cwd()}),s=i.map(n=>`- ${n.name}${n.trigger?` (${n.trigger})`:""} [${n.source}]: ${n.description}`);return{status:"ok",message:s.length>0?s.join(`
|
|
3
|
-
`):"No skills found",data:i}}default:return{status:"unsupported",message:`Unknown command: ${e}`}}}catch(i){return{status:"failed",message:i instanceof Error?i.message:String(i)}}}get acpSessionOptions(){const e=this.acpClient?.sessionOptions??null,t=this.isReasonix?B:this.isKimi?R:null;if(!t||e&&e.models.length>0)return e;const i=this.resolveFallbackCurrentModelId(t,e?.currentModelId??"");return{modes:e?.modes??[],currentModeId:e?.currentModeId??"",models:[...t],currentModelId:i}}resolveFallbackCurrentModelId(e,t){if(e.some(s=>s.modelId===t))return t;const i=(this.autoInjectArgs?.model??this.acpInitialModel??"").trim();return i&&e.some(s=>s.modelId===i)?i:e[0]?.modelId??""}get isReasonix(){return(this.config.command??"").includes("reasonix")}get isKimi(){return(this.config.command??"").includes("kimi")}buildToolbarContext(e,t){const i=this.acpClient?.sessionOptions,s=i?.currentModeId??"";let n=i?.currentModelId??"";const o=i?.modes.map(m=>({id:m.id,name:m.name}))??[],a=i?.models&&i.models.length>0,l=a?i.models:this.isReasonix?B:this.isKimi?R:[],d=a?"acp":this.isReasonix?"reasonix_fallback":this.isKimi?"kimi_fallback":"empty";this.isKimi&&!a&&r.warn("acp-adapter",`[toolbar-diag] kimi ACP session returned no models; using static fallback. command=${this.config.command} fallbackIds=${JSON.stringify(R.map(m=>m.modelId))}`),r.info("acp-adapter",`[toolbar-diag] buildToolbarContext outcome=${e} agent=${this.config.command} modelSource=${d} acpModelCount=${i?.models?.length??0} finalModelCount=${l.length}`);const p=l.map(m=>({modelId:m.modelId,name:m.name})),u=l.map(m=>({id:m.modelId,displayName:m.name})),_=i?.modes.map(m=>({id:m.id,displayName:m.name}))??[];u.length>0&&!u.some(m=>m.id===n)&&(n=this.resolveFallbackCurrentModelId(l,n));const y={outcome:e,...t?{cwd:t}:{},model_id:n,mode_id:s,currentModelId:n,currentModeId:s,available_models:u,available_modes:_,availableModels:u,availableModes:_,models:p,modes:o};return r.info("acp-adapter",`[toolbar] buildToolbarContext outcome=${e} model_id="${n}" available_models=${JSON.stringify(u.map(m=>m.id))} currentModelId=${n}`),y}get pendingApprovalEntries(){return this.pendingApprovals}get hasSessionBinding(){return this.bindingStore!==null}setMode(e){return this.acpClient?this.acpClient.setLiveMode(e):Promise.resolve(!1)}setModel(e){return this.acpClient?this.acpClient.setModel(e):Promise.resolve(!1)}handleAcpApprovalAction(e,t){const i=this.pendingApprovals.get(e);return i?(r.info("acp-adapter","handleAcpApprovalAction resolving",{toolCallId:e,requestId:i,behavior:t}),this.pendingApprovals.delete(e),this.acpClient&&this.acpClient.respondPermission(i,{behavior:t}).catch(s=>{r.error("acp-adapter",`Failed to respond to permission: ${s}`)}),this.activeRun&&(this.activeRun.lastProgressAt=Date.now(),this.resetIdleTimer(this.activeRun)),!0):(r.warn("acp-adapter","handleAcpApprovalAction: no pending approval",{toolCallId:e,behavior:t,pendingCount:this.pendingApprovals.size}),!1)}handleExecApprovalEvent(e){const t=String(e.tool_call_id??e.approval_command_id??e.approval_id??e.exec_context_id??"");return t?this.handleAcpApprovalAction(t,e.decision)?{delivered:!0}:{delivered:!1,errorCode:"approval_not_found",errorMsg:`no pending approval for tool_call_id: ${t}`}:{delivered:!1,errorCode:"approval_id_required",errorMsg:"approval id is required"}}respondToPermission(e,t){this.acpClient&&this.acpClient.respondPermission(e,t).catch(i=>{r.error("acp-adapter",`Failed to respond to permission: ${i}`)}),this.activeRun&&(this.activeRun.lastProgressAt=Date.now(),this.resetIdleTimer(this.activeRun))}async handleLocalAction(e){const t=e.action_type??"",i=e.params??{};if(t==="claude_interaction_reply"&&String(i.kind??"")==="permission"){const s=i.resolution&&typeof i.resolution=="object"?i.resolution:{},n=String(s.value??i.decision??"");return["allow","allow-once","allow-always","deny"].includes(n)?this.handleLocalAction({...e,action_type:n==="deny"?"permission_reject":"permission_approve",params:{...i,approval_command_id:i.request_id,decision:n}}):(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"permission_decision_invalid","permission decision is invalid"),{handled:!0,kind:"approval"})}if(t==="claude_interaction_reply")return this.handleAcpQuestionReply(e);if(t==="exec_approve"||t==="exec_reject"||t==="permission_approve"||t==="permission_reject"){const s=String(i.tool_call_id??i.approval_command_id??i.approval_id??i.exec_context_id??""),n=t==="exec_approve"||t==="permission_approve",o=String(i.decision??"");let a;return n&&(o==="allow-once"||o==="allow-always")?a=o:n?a="allow":a="deny",s?this.handleAcpApprovalAction(s,a)?(this.callbacks.sendLocalActionResult(e.action_id,"ok"),{handled:!0,kind:"approval"}):(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"approval_not_found",`no pending approval for tool_call_id: ${s}`),{handled:!0,kind:"approval"}):(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"tool_call_id_required","tool_call_id is required"),{handled:!0,kind:"approval"})}return{handled:!1,kind:""}}async handleQuestionReplyEvent(e){let t;if(e.action)t={type:"action",value:e.action};else if(e.response?.type==="single")t={type:"text",value:e.response.value};else if(e.response?.type==="map")t={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:t})}async handleAcpQuestionReply(e){const t=await this.deliverQuestionReply(e.params??{});return t.delivered?(this.callbacks.sendLocalActionResult(e.action_id,"ok",t.result),{handled:!0,kind:t.kind??"question_reply"}):(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,t.errorCode,t.errorMsg),{handled:!0,kind:t.kind??"question_reply_failed"})}async deliverQuestionReply(e){const t=String(e.request_id??"").trim(),i=this.pendingQuestions.get(t);if(!t||!i||!this.acpClient)return{delivered:!1,errorCode:"interaction_request_not_pending",errorMsg:"The question is no longer pending; the reply was not delivered.",kind:"question_reply_not_pending"};const s=e.resolution&&typeof e.resolution=="object"?e.resolution:{},n=String(s.type??"");let o;if(n==="action"&&String(s.value??"")==="cancel")o=i.options.find(l=>/_skip$/.test(l.optionId)||l.kind.toLowerCase().includes("reject"));else{let l="";if(n==="text")l=String(s.value??"").trim();else if(n==="map"){const d=Array.isArray(s.entries)?s.entries:[];l=String(d[0]?.value??"").trim()}o=i.options.find(d=>d.name===l||d.optionId===l)}if(!o)return{delivered:!1,errorCode:"question_answer_invalid",errorMsg:"The selected answer does not match a pending question option.",kind:"question_reply_invalid"};try{await this.acpClient.respondPermissionOption(t,o.optionId)}catch(l){return{delivered:!1,errorCode:"question_reply_failed",errorMsg:l instanceof Error?l.message:String(l),kind:"question_reply_failed"}}this.pendingQuestions.delete(t),this.activeRun&&(this.activeRun.lastProgressAt=Date.now(),this.resetIdleTimer(this.activeRun));const a=/_skip$/.test(o.optionId);return{delivered:!0,result:{request_id:t,resolution:a?"cancel":"answer"},kind:a?"question_reply_cancel":"question_reply"}}resolveCwd(){if(this.currentAibotSessionId){const e=this.sessionBindings.get(this.currentAibotSessionId);if(e)return e}if(this.bindingStore&&this.currentAibotSessionId){const e=this.bindingStore.get(this.currentAibotSessionId);if(e?.cwd)return e.cwd}return process.cwd()}async bindSession(e,t){const i=this.sessionBindings.get(e);if(i)try{return await g.stat(i),!1}catch{r.info("acp-adapter",`Stale binding for session ${e}: ${i} no longer exists, allowing rebind to ${t}`),this.sessionBindings.delete(e)}return this.sessionBindings.set(e,t),this.bindingStore&&this.bindingStore.set(e,t),this.maybeRefreshKiroSkills(t),!this.sessionConnected&&this.agentProcess?.alive?this.connectSession(t).then(()=>!0).catch(s=>{r.error("acp-adapter",`Failed to create session on bind: ${I(s)}`);const n=this.buildToolbarContext("bind_failed",t),o=Array.isArray(n.available_models)&&n.available_models.length>0;return this.callbacks.sendUpdateBindingCard(e,"failed",t,o?n:void 0),!0}):(this.acpClient?.isAlive&&(this.bindingStore&&this.acpClient.sessionId&&this.bindingStore.setAcpSessionId(e,this.acpClient.sessionId),this.callbacks.sendUpdateBindingCard(e,"connected",t,this.buildToolbarContext("binding_ready",t))),Promise.resolve(!0))}getSessionCwd(e){return this.sessionBindings.get(e)}getSessionBindings(){return this.sessionBindings}maybeRefreshKiroSkills(e){if(this.config.command!=="kiro-cli"||!this.callbacks.onSkillsUpdate)return;const t=String(e??"").trim()||void 0;let i=[];try{i=O({mode:"kiro",projectDir:t})}catch(s){r.warn("acp-adapter",`Kiro skills scan failed: ${s instanceof Error?s.message:String(s)}`);return}try{this.callbacks.onSkillsUpdate(i,t)}catch(s){r.warn("acp-adapter",`onSkillsUpdate failed: ${s instanceof Error?s.message:String(s)}`)}}replayDeferredEvents(e){const t=this.deferredEvents.get(e);if(!t||t.length===0)return;if(this.activeRun){r.info("acp-adapter",`Cannot replay deferred events for session ${e}: agent busy`);return}const i=t.shift();i&&(t.length===0?(this.deferredEvents.delete(e),this.cancelDeferredTimer(e)):this.deferredEvents.set(e,t),r.info("acp-adapter",`Replaying deferred event ${i.event.event_id} for session ${e} (remaining: ${t.length})`),this.startRun(i.event,!1))}finishCompaction(e){if(!this.compacting)return;this.compacting=!1,this.compactingTimer&&(clearTimeout(this.compactingTimer),this.compactingTimer=null),this.suppressOrphanOutput(W),r.info("acp-adapter",`Compaction finished (${e}); replaying deferred events`),this.replayNextDeferredEvent();const t=this.compactionDoneResolver;this.compactionDoneResolver=null,t?.()}replayNextDeferredEvent(){if(this.activeRun||this.compacting)return;const e=this.deferredEvents.keys().next().value;e&&this.replayDeferredEvents(e)}announceDeferredComposing(e){}sendTerminalEventWithoutRun(e,t,i){this.emit("eventDone",e),this.callbacks.sendEventResult(e,t,i)}deliverInboundEvent(e){if(this.eventResults?.has(e.session_id,e.event_id)){const t=this.eventResults.get(e.session_id,e.event_id);r.info("acp-adapter",`Deduplicating event ${e.event_id} (cached: ${t.status})`),this.sendTerminalEventWithoutRun(e.event_id,t.status,t.msg);return}if(this.compacting){r.info("acp-adapter",`Event ${e.event_id} deferred: compaction in progress`),this.deferEvent(e);return}if(this.activeRun){r.info("acp-adapter",`Event ${e.event_id} rejected: busy`),this.sendTerminalEventWithoutRun(e.event_id,"failed","agent busy");return}if(!this.agentProcess?.alive){r.warn("acp-adapter",`Event ${e.event_id} cannot start: agent process not alive; requesting session recovery`),this.emit("internalError",{eventId:e.event_id,sessionId:e.session_id,errorMsg:"agent not alive",replayOriginalContent:!0});return}e.session_id&&e.session_id!==this.currentAibotSessionId&&(this.currentAibotSessionId=e.session_id),this.startRun(e,!1)}deliverStopEvent(e,t){this.acpClient?(this.acpClient.cancel().catch(()=>{}),this.flushStream()):this.flushStream(),this.activeRun?.eventId===e&&this.finishRun("canceled","stopped by user")}deferEvent(e){const t=this.deferredEvents.get(e.session_id)??[];t.some(i=>i.event.event_id===e.event_id)||(t.push({event:e,queuedAt:Date.now()}),this.deferredEvents.set(e.session_id,t),r.info("acp-adapter",`Deferred event ${e.event_id} for session ${e.session_id} (queue: ${t.length})`),this.scheduleDeferredTimeout(e.session_id))}deferredTimers=new Map;rejectDeferredEvents(e){for(const[t,i]of this.deferredEvents)for(const{event:s}of i)r.info("acp-adapter",`Rejecting deferred event ${s.event_id}: ${e}`),this.sendTerminalEventWithoutRun(s.event_id,"failed",e);this.deferredEvents.clear(),this.cancelAllDeferredTimers()}cancelDeferredTimer(e){const t=this.deferredTimers.get(e);t&&(clearTimeout(t),this.deferredTimers.delete(e))}cancelAllDeferredTimers(){for(const e of this.deferredTimers.values())clearTimeout(e);this.deferredTimers.clear()}scheduleDeferredTimeout(e){if(this.deferredTimers.has(e))return;const t=3e4,i=setTimeout(()=>{this.deferredTimers.delete(e);const s=this.deferredEvents.get(e);if(!(!s||s.length===0)&&!this.acpClient?.isAlive){r.error("acp-adapter",`Deferred events for session ${e} timed out after ${t/1e3}s (no ACP client)`),this.deferredEvents.delete(e);for(const{event:n}of s)this.sendTerminalEventWithoutRun(n.event_id,"failed","agent initialization timed out")}},t);this.deferredTimers.set(e,i)}startRun(e,t){if(!this.acpClient?.isAlive){this.deferEvent(e);return}const i=`acp_${++this.clientMsgSeq}_${Date.now()}`;this.activeRun={eventId:e.event_id,sessionId:e.session_id,threadId:e.thread_id,clientMsgIdBase:i,currentClientMsgId:i,currentSegmentIndex:0,chunkSeq:0,buffer:"",quotedStream:new C,markdownSegmenter:new re,quotedMessageId:void 0,flushTimer:null,idleTimer:null,awaitingToolResult:!1,responded:!1,silent:t,lastProgressAt:Date.now(),lastIdleCheckAt:Date.now(),idleNoProgressCount:0,consecutivePingFailures:0,lastToolProgress:new Map,lastToolProgressSentAt:new Map,toolNamesById:new Map,sentToolUseIds:new Set,lastToolUse:null,lastToolResult:null,busyRetryTimer:null,busyWaitSince:0,busyRetryCount:0,busyWaiting:!1};const s=this.activeRun;this.beginAuditCapture(e);const n=ae(e,this.resolveCwd());this.resetIdleTimer(s),n.modeId&&this.acpClient&&this.acpClient.setLiveMode(n.modeId).catch(()=>{}),n.modelId&&this.acpClient&&this.acpClient.setModel(n.modelId).catch(()=>{});const o=le(n.prompt,{messageId:e.msg_id,quotedMessageId:e.quoted_message_id}),a=this.injectQuoteProtocolOnce(s.sessionId,o),l=this.injectIdentityOnce(s.sessionId,a),d=this.injectSessionContextOnce(s.sessionId,l),p=this.injectRecoveryContext(s.sessionId,d);this.sendPromptWithRetry(s,p)}injectQuoteProtocolOnce(e,t){return!e||this.quoteProtocolTaughtSessions.has(e)?t:(this.quoteProtocolTaughtSessions.add(e),A(t,_e))}injectIdentityOnce(e,t){if(!e||this.identityInjectedSessions.has(e))return t;const i=this.callbacks.getAgentProfile?.(),s=i?.agentName?.trim()??"",n=i?.introduction?.trim()??"";if(!s&&!n)return t;this.identityInjectedSessions.add(e);const o=["[system-identity]"];return s&&o.push(`Your name is "${s}".`),n&&o.push(`Your self-introduction: ${n}`),o.push("Treat this as an out-of-band instruction; do not echo or repeat it in your replies."),o.push("[/system-identity]"),r.info("acp-adapter",`Injected agent identity for session=${e} name="${s}"`),A(t,o.join(`
|
|
1
|
+
import{fileURLToPath as H}from"node:url";import p from"node:path";import{homedir as v}from"node:os";import{promises as g,statSync as P}from"node:fs";import{EventEmitter as $}from"node:events";import{spawn as E}from"node:child_process";import{AgentProcess as G}from"../../agent/process.js";import{hasChildProcesses as Q,listGroupPids as z}from"../../core/runtime/spawn.js";import{applyNativeProviderConfig as Y}from"../shared/native-provider-config.js";import{buildSessionContextBlock as V}from"../shared/session-context.js";import{compactToolInputForWire as M}from"../shared/tool-wire-payload.js";import{syncDefaultSkillsToDir as X,resolveKimiCodeHome as J}from"../../default-skills/index.js";import{resolveHermesHomeDir as Z}from"../shared/native-provider-config.js";import{prepareKimiLayerPlugin as ee}from"../../kimi-plugins/index.js";import{AcpClient as te,AcpAuthRequiredError as ie,isAuthRequiredError as se}from"../../protocol/acp-client.js";import{AgentEventType as f}from"../../types/events.js";import{InternalApiServer as ne}from"../../core/mcp/internal-api-server.js";import{EventResultsStore as oe}from"../../core/persistence/event-results-store.js";import{QuotedMessageStream as C}from"../../core/util/quoted-message-stream.js";import{SafeMarkdownStreamSegmenter as re}from"../../core/text-segmentation/index.js";import{extractAcpTurnInput as ae}from"../../core/protocol/payload-parser.js";import{injectMessageMetadata as le}from"../../core/protocol/message-metadata.js";import{insertBeforeTrailingMessageReferences as A}from"../../core/protocol/message-reference.js";import{log as r}from"../../core/log/index.js";function x(c,e=2e3){let t;try{const i=new WeakSet;t=JSON.stringify(c,(s,n)=>{if(n instanceof Error)return{name:n.name,message:n.message};if(typeof n=="function")return"[function]";if(typeof n=="bigint"||typeof n=="symbol")return n.toString();if(typeof n=="object"&&n!==null){if(i.has(n))return"[circular]";i.add(n)}return n})}catch(i){t=`[stringify failed: ${i instanceof Error?i.message:String(i)}]`}return t===void 0?"[undefined]":t.length<=e?t:`${t.slice(0,e)}...[+${t.length-e}b]`}const ce=5e3;function N(c,e){const t=e.toolName??"";return e.toolCallId&&(!t||t===e.toolCallId)?c.toolNamesById.get(e.toolCallId)??t:t}function S(c){const e=String(c??"").trim();if(!e)return"";try{const t=JSON.parse(e);return JSON.stringify(_(t))}catch{return e}}function _(c){if(c===null||typeof c!="object")return c;if(Array.isArray(c))return c.map(_);const e=c,t=Object.keys(e).sort(),i={};for(const s of t)i[s]=_(e[s]);return i}function q(c){const e=Array.isArray(c)?c:[c],t=[];for(const i of e){if(!i||typeof i!="object")continue;const s=i;if(typeof s.text=="string"&&s.text.trim()){t.push(s.text.trim());continue}const n=s.content;if(n&&typeof n=="object"&&!Array.isArray(n)){const o=n.text;typeof o=="string"&&o.trim()&&t.push(o.trim())}}return t.join(`
|
|
2
|
+
`)}function de(c,e,t){const i=c&&typeof c=="object"?c:{},s=i.toolCall&&typeof i.toolCall=="object"?i.toolCall:{},n=[s.rawInput,s.raw_input,s.input,s.arguments,s.args,q(s.content),s.content,s.description,i.tool_input,i.toolInput,e];for(const o of n){const a=M(o);if(a&&a!==t)return a}return""}function ue(c){if(!`${c.toolName} ${c.toolTitle}`.toLowerCase().replace(/[^a-z]/g,"").includes("askuserquestion"))return null;const t=c.options.filter(o=>/^q\d+_opt_\d+$/.test(o.optionId));if(t.length===0)return null;const i=c.rawParams&&typeof c.rawParams=="object"?c.rawParams:{},s=i.toolCall&&typeof i.toolCall=="object"?i.toolCall:{},n=q(s.content).trim();return n?{prompt:n,answerOptions:t}:null}import{scanSkills as O}from"../claude/skill-scanner.js";import{checkKiroSessionActivity as he,checkKimiSessionActivity as pe,hasRunningKimiDetachedTask as me}from"./session-activity.js";import{resolveCliPath as fe,getCliVersion as ge}from"../../core/util/cli-probe.js";const w=p.dirname(H(import.meta.url)),B=[{modelId:"deepseek-v4-pro",name:"DeepSeek V4 Pro"},{modelId:"deepseek-v4-flash",name:"DeepSeek V4 Flash"}],R=[{modelId:"kimi-code/kimi-for-coding",name:"K2.7 Coding"},{modelId:"kimi-code/kimi-for-coding-highspeed",name:"K2.7 Coding Highspeed"},{modelId:"kimi-code/k3",name:"K3"}];function k(c,e){const t=Number(process.env[c]);return Number.isFinite(t)&&t>0?t:e}const ve=200,Ie=60*1e3,ye=600*1e3,T=k("GRIX_ACP_TURN_INACTIVITY_TIMEOUT_MS",900*1e3),D=2,F=5e3,U=80,j=1200*1e3,Ce=k("GRIX_ACP_BUSY_RETRY_INTERVAL_MS",5e3),L=k("GRIX_ACP_BUSY_WAIT_MAX_MS",1800*1e3),Ae=6e4,Se=2e3,Te=12e3,K=3e3,W=3e4,be=["[grix protocol] Markers like [[message_id:<id>]] and [[quoted_message_id:<id>]] are metadata, not user prose \u2014 never repeat them verbatim except as instructed here.","The message you are replying to carries its id in a [[message_id:<id>]] marker.","If (and only if) you want your reply to quote/thread onto that specific message, include [[quoted_message_id:<id>]] anywhere in your reply using that id; otherwise omit it."].join(" ");function _e(c){return!(!(c instanceof Error)||c.code!==-32603||se(c))}function we(c){if(!(c instanceof Error))return!1;const e=c.data;return e&&typeof e=="object"&&e.code==="turn.agent_busy"?!0:/cannot launch a new turn/i.test(c.message)}function I(c){if(!(c instanceof Error))return String(c);let e=c.message;const t=c.data;return t&&(e+=` data=${JSON.stringify(t)}`),e}function Re(c){return c.replace(/\u001b\[[0-9;?]*[ -/]*[@-~]/g,"")}class it extends ${type="acp";config;callbacks;agentProcess=null;acpClient=null;internalApi=null;activeRun=null;compacting=!1;compactingTimer=null;compactionDoneResolver=null;pendingAutoCompact=!1;eventResults=null;pendingApprovals=new Map;pendingQuestions=new Map;clientMsgSeq=0;stopped=!1;acpAuthMethod;acpInitialMode;acpInitialModel;acpMcpTools;terminalAuthChild=null;rawTransport;approvalMode;autoInjectArgs;nativeProviderScope;reasonixAuditTranscriptPath;auditAgentType;trimToolWirePayload;activeAuditEventId=null;auditCaptures=new Map;completedAuditBoundaries=new Map;bindingStore;sessionBindings=new Map;deferredEvents=new Map;currentAibotSessionId;bridgeLog;sessionConnected=!1;sessionContextPending=!1;baselineGroupPids=[];rawEventSeq=0;recoveryContextBySessionId=new Map;quoteProtocolTaughtSessions=new Set;identityInjectedSessions=new Set;orphanBuffer="";orphanQuotedStream=new C;orphanFlushTimer=null;orphanSessionId=null;orphanSuppressedUntil=0;constructor(e,t,i){super(),this.config=e,this.callbacks=t,this.bridgeLog=i?.bridgeLog??null,this.acpAuthMethod=i?.acpAuthMethod,this.acpInitialMode=i?.acpInitialMode,this.acpInitialModel=i?.acpInitialModel,this.acpMcpTools=i?.acpMcpTools??!0,this.rawTransport=i?.rawTransport??!1,this.approvalMode=i?.approvalMode??"default",this.autoInjectArgs=i?.autoInjectArgs,this.nativeProviderScope=i?.nativeProviderScope,this.reasonixAuditTranscriptPath=i?.reasonixAuditTranscriptPath,this.auditAgentType=String(i?.agentType??p.basename(e.command)).trim().toLowerCase();const s=p.basename(e.command).toLowerCase(),n=this.auditAgentType==="copilot"||/^copilot(?:\.cmd)?$/i.test(s)||/^gh(?:\.exe)?$/i.test(s)&&(e.args??[]).includes("copilot");if(this.trimToolWirePayload=["kimi","kiro","qwen","reasonix","copilot"].includes(this.auditAgentType)||s.includes("kimi")||s==="kiro-cli"||/^qwen(?:\.cmd)?$/i.test(s)||s.includes("reasonix")||n,this.bindingStore=i?.bindingStore??null,this.currentAibotSessionId=i?.aibotSessionId?String(i.aibotSessionId).trim():void 0,i?.eventResultsPath&&(this.eventResults=new oe(i.eventResultsPath)),this.bindingStore&&this.currentAibotSessionId){const o=this.bindingStore.get(this.currentAibotSessionId);o?.cwd&&this.sessionBindings.set(this.currentAibotSessionId,o.cwd)}}async start(){if(await this.spawnProcess(),!this.bindingStore){await this.connectSession(this.resolveCwd());return}const e=this.currentAibotSessionId?this.sessionBindings.get(this.currentAibotSessionId):void 0;e&&await this.connectSession(e)}async stop(){this.stopped=!0,this.rejectDeferredEvents("adapter stopped"),this.cancelAllDeferredTimers(),this.pendingAutoCompact=!1,this.compacting=!1,this.compactingTimer&&(clearTimeout(this.compactingTimer),this.compactingTimer=null),this.compactionDoneResolver&&(this.compactionDoneResolver(),this.compactionDoneResolver=null),this.activeRun&&this.finishRun("canceled","adapter stopped"),this.flushOrphanBuffer(),this.acpClient&&(this.acpClient.dispose(),this.acpClient.removeAllListeners(),this.acpClient=null),this.agentProcess&&(await this.agentProcess.close(),this.agentProcess=null),this.baselineGroupPids=[],this.internalApi&&(await this.internalApi.stop(),this.internalApi=null),this.terminalAuthChild&&(this.terminalAuthChild.kill("SIGKILL"),this.terminalAuthChild=null)}isAlive(){return this.agentProcess?.alive??!1}async createSession(e){return this.acpClient?.sessionId??""}async resumeSession(e,t){}async destroySession(e){this.quoteProtocolTaughtSessions.delete(e),this.identityInjectedSessions.delete(e)}onAgentProfileChanged(){this.identityInjectedSessions.size>0&&(r.info("acp-adapter",`agent profile changed; clearing ${this.identityInjectedSessions.size} identity-injected session(s)`),this.identityInjectedSessions.clear())}sendPrompt(e){const t=new ke(e.adapterSessionId);return this.acpClient?.isAlive&&this.acpClient.send(e.text).catch(i=>{t.emitError(i instanceof Error?i:new Error(String(i)))}),t}async cancel(e){this.activeRun&&this.acpClient&&(await this.acpClient.cancel(),this.flushStream(),this.finishRun("canceled","stopped by user"),this.suppressOrphanOutput(W))}setPermissionHandler(e){}async ping(e){return this.acpClient?.isAlive?this.acpClient.ping(e):this.agentProcess?.alive??!1}getStatus(){return{alive:this.agentProcess?.alive??!1,busy:this.activeRun!==null||this.compacting,sessions:this.sessionConnected?1:0}}getActiveEventIds(){return this.activeRun?[this.activeRun.eventId]:[]}clearActiveEventForShutdown(){this.activeRun&&(this.snapshotAuditBoundary(this.activeRun.eventId),this.clearRunTimers(this.activeRun),this.activeRun=null)}takeAuditBoundary(e){const t=this.completedAuditBoundaries.get(e);return t&&this.completedAuditBoundaries.delete(e),t}clearRunTimers(e){e.flushTimer&&(clearTimeout(e.flushTimer),e.flushTimer=null),e.idleTimer&&(clearTimeout(e.idleTimer),e.idleTimer=null),e.busyRetryTimer&&(clearTimeout(e.busyRetryTimer),e.busyRetryTimer=null)}getMcpConfig(){if(!this.internalApi)return null;this.internalApi.setMcpBridgeUpHandler(t=>this.onMcpBridgeUp(t));const e=p.resolve(w,"../../mcp/mcp-bridge-server.js");return{name:"grix-app-bridge",command:process.execPath,args:[e,"--ws-url",this.internalApi.mcpBridgeWsUrl]}}async hasBackgroundWork(){const e=this.agentProcess?.pid;return e?Q(e,[e,...this.baselineGroupPids]):!1}captureBaselineGroupPids(){const e=this.agentProcess?.pid;e&&z(e).then(t=>{this.baselineGroupPids=t}).catch(()=>{})}async probe(e){const t=this.config.command||"gemini",i=await fe(t),s=i!==null;let n=null,o;if(s){const h=await ge(t);n=h.version,h.error&&(o=h.error)}else o={code:"cli_not_found",message:`command not found: ${t}`};const a=this.acpClient?.model??null,l=this.getStatus();let d;return e?.conversation?this.acpClient?.isAlive?d={attempted:!0,ok:!0,latency_ms:null}:d={attempted:!0,ok:!1,latency_ms:null,error:{code:"unsupported",message:"ACP session not connected, cannot probe conversation"}}:d={attempted:!1,ok:!1,latency_ms:null},{cli:{command:t,installed:s,path:i,version:n,...o?{error:o}:{}},conversation:d,config:{model:a,base_url:null,source:{model:a?"runtime":"unknown",base_url:"unknown"}},process:{started:!!this.agentProcess,alive:l.alive,busy:l.busy}}}getSupportedCommands(){return this.acpClient?.availableCommands?.length?this.acpClient.availableCommands.map(e=>({name:e.name,description:e.description??"",args:e.args})):[{name:"model",description:"List or set model",args:"[model_id]"},{name:"mode",description:"List or set collaboration mode",args:"[mode_id]"},{name:"interrupt",description:"Interrupt current run"},{name:"compact",description:"\u538B\u7F29\u4E0A\u4E0B\u6587"},{name:"status",description:"Show session status"},{name:"skills",description:"List available skills"}]}async execCommand(e,t,i){return this.acpClient?.supportsCommandsExecute?this.execCommandViaAcp(e,t):this.execCommandFallback(e,t)}async execCommandViaAcp(e,t){if(!this.acpClient)return{status:"failed",message:"No active ACP session"};if(e==="interrupt")return this.activeRun?(await this.cancel(this.activeRun.sessionId),{status:"ok",message:"Run interrupted"}):{status:"failed",message:"No active run to interrupt"};if(e==="compact"){if(this.compacting)return{status:"failed",message:"Compaction already in progress"};if(this.activeRun)return{status:"failed",message:"agent busy"};this.compacting=!0;try{const i=await this.acpClient.executeCommand(e,t||void 0);return this.finishCompaction("acp-commands-execute"),{status:i.status==="failed"?"failed":"ok",message:i.message??"Compacted",data:i.data}}catch(i){return this.finishCompaction("error"),i?.code===-32601?this.execCommandFallback(e,t):{status:"failed",message:`compact failed: ${i instanceof Error?i.message:i}`}}}try{const i=await this.acpClient.executeCommand(e,t||void 0);return{status:i.status??"ok",message:i.message,data:i.options?{command:e,options:i.options}:i.data}}catch(i){return i?.code===-32601?this.execCommandFallback(e,t):{status:"failed",message:`Command failed: ${i instanceof Error?i.message:i}`}}}async execCommandFallback(e,t){try{switch(e){case"model":{const i=t.trim();if(i)return await this.setModel(i)?{status:"ok",message:`Model set to ${i}`}:{status:"failed",message:`Failed to set model: ${i}`};const s=this.buildToolbarContext("model_list");return{status:"ok",message:`Current: ${s.currentModelId||"unknown"}`,data:s}}case"mode":{const i=t.trim();if(i)return await this.setMode(i)?{status:"ok",message:`Mode set to ${i}`}:{status:"failed",message:`Failed to set mode: ${i}`};const s=this.buildToolbarContext("mode_list");return{status:"ok",message:`Current: ${s.currentModeId||"unknown"}`,data:s}}case"interrupt":return this.activeRun?this.acpClient?(await this.cancel(this.activeRun.sessionId),{status:"ok",message:"Run interrupted"}):{status:"failed",message:"Not connected"}:{status:"failed",message:"No active run to interrupt"};case"compact":{if(!this.acpClient)return{status:"failed",message:"No active ACP session"};if(this.compacting)return{status:"failed",message:"Compaction already in progress"};if(this.activeRun)return{status:"failed",message:"agent busy"};this.compacting=!0;const i=new Promise(s=>{this.compactionDoneResolver=s});try{await this.acpClient.send("/compact")}catch(s){throw this.finishCompaction("send-failed"),s}return this.compactingTimer=setTimeout(()=>this.finishCompaction("timeout"),15e3),this.compactingTimer.unref?.(),await i,{status:"ok",message:"Compacted"}}case"status":{const i=this.getStatus(),s=this.acpClient?.sessionOptions;return{status:"ok",message:`Alive: ${i.alive}, Busy: ${i.busy}, Model: ${s?.currentModelId??"unknown"}, Mode: ${s?.currentModeId??"unknown"}`,data:{alive:i.alive,busy:i.busy,sessions:i.sessions,model:s?.currentModelId??"",mode:s?.currentModeId??""}}}case"skills":{const i=O({mode:this.config.command==="kiro-cli"?"kiro":"gemini",projectDir:process.cwd()}),s=i.map(n=>`- ${n.name}${n.trigger?` (${n.trigger})`:""} [${n.source}]: ${n.description}`);return{status:"ok",message:s.length>0?s.join(`
|
|
3
|
+
`):"No skills found",data:i}}default:return{status:"unsupported",message:`Unknown command: ${e}`}}}catch(i){return{status:"failed",message:i instanceof Error?i.message:String(i)}}}get acpSessionOptions(){const e=this.acpClient?.sessionOptions??null,t=this.isReasonix?B:this.isKimi?R:null;if(!t||e&&e.models.length>0)return e;const i=this.resolveFallbackCurrentModelId(t,e?.currentModelId??"");return{modes:e?.modes??[],currentModeId:e?.currentModeId??"",models:[...t],currentModelId:i}}resolveFallbackCurrentModelId(e,t){if(e.some(s=>s.modelId===t))return t;const i=(this.autoInjectArgs?.model??this.acpInitialModel??"").trim();return i&&e.some(s=>s.modelId===i)?i:e[0]?.modelId??""}get isReasonix(){return(this.config.command??"").includes("reasonix")}get isKimi(){return(this.config.command??"").includes("kimi")}buildToolbarContext(e,t){const i=this.acpClient?.sessionOptions,s=i?.currentModeId??"";let n=i?.currentModelId??"";const o=i?.modes.map(m=>({id:m.id,name:m.name}))??[],a=i?.models&&i.models.length>0,l=a?i.models:this.isReasonix?B:this.isKimi?R:[],d=a?"acp":this.isReasonix?"reasonix_fallback":this.isKimi?"kimi_fallback":"empty";this.isKimi&&!a&&r.warn("acp-adapter",`[toolbar-diag] kimi ACP session returned no models; using static fallback. command=${this.config.command} fallbackIds=${JSON.stringify(R.map(m=>m.modelId))}`),r.info("acp-adapter",`[toolbar-diag] buildToolbarContext outcome=${e} agent=${this.config.command} modelSource=${d} acpModelCount=${i?.models?.length??0} finalModelCount=${l.length}`);const h=l.map(m=>({modelId:m.modelId,name:m.name})),u=l.map(m=>({id:m.modelId,displayName:m.name})),b=i?.modes.map(m=>({id:m.id,displayName:m.name}))??[];u.length>0&&!u.some(m=>m.id===n)&&(n=this.resolveFallbackCurrentModelId(l,n));const y={outcome:e,...t?{cwd:t}:{},model_id:n,mode_id:s,currentModelId:n,currentModeId:s,available_models:u,available_modes:b,availableModels:u,availableModes:b,models:h,modes:o};return r.info("acp-adapter",`[toolbar] buildToolbarContext outcome=${e} model_id="${n}" available_models=${JSON.stringify(u.map(m=>m.id))} currentModelId=${n}`),y}get pendingApprovalEntries(){return this.pendingApprovals}get hasSessionBinding(){return this.bindingStore!==null}setMode(e){return this.acpClient?this.acpClient.setLiveMode(e):Promise.resolve(!1)}setModel(e){return this.acpClient?this.acpClient.setModel(e):Promise.resolve(!1)}handleAcpApprovalAction(e,t){const i=this.pendingApprovals.get(e);return i?(r.info("acp-adapter","handleAcpApprovalAction resolving",{toolCallId:e,requestId:i,behavior:t}),this.pendingApprovals.delete(e),this.acpClient&&this.acpClient.respondPermission(i,{behavior:t}).catch(s=>{r.error("acp-adapter",`Failed to respond to permission: ${s}`)}),this.activeRun&&(this.activeRun.lastProgressAt=Date.now(),this.resetIdleTimer(this.activeRun)),!0):(r.warn("acp-adapter","handleAcpApprovalAction: no pending approval",{toolCallId:e,behavior:t,pendingCount:this.pendingApprovals.size}),!1)}handleExecApprovalEvent(e){const t=String(e.tool_call_id??e.approval_command_id??e.approval_id??e.exec_context_id??"");return t?this.handleAcpApprovalAction(t,e.decision)?{delivered:!0}:{delivered:!1,errorCode:"approval_not_found",errorMsg:`no pending approval for tool_call_id: ${t}`}:{delivered:!1,errorCode:"approval_id_required",errorMsg:"approval id is required"}}respondToPermission(e,t){this.acpClient&&this.acpClient.respondPermission(e,t).catch(i=>{r.error("acp-adapter",`Failed to respond to permission: ${i}`)}),this.activeRun&&(this.activeRun.lastProgressAt=Date.now(),this.resetIdleTimer(this.activeRun))}async handleLocalAction(e){const t=e.action_type??"",i=e.params??{};if(t==="claude_interaction_reply"&&String(i.kind??"")==="permission"){const s=i.resolution&&typeof i.resolution=="object"?i.resolution:{},n=String(s.value??i.decision??"");return["allow","allow-once","allow-always","deny"].includes(n)?this.handleLocalAction({...e,action_type:n==="deny"?"permission_reject":"permission_approve",params:{...i,approval_command_id:i.request_id,decision:n}}):(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"permission_decision_invalid","permission decision is invalid"),{handled:!0,kind:"approval"})}if(t==="claude_interaction_reply")return this.handleAcpQuestionReply(e);if(t==="exec_approve"||t==="exec_reject"||t==="permission_approve"||t==="permission_reject"){const s=String(i.tool_call_id??i.approval_command_id??i.approval_id??i.exec_context_id??""),n=t==="exec_approve"||t==="permission_approve",o=String(i.decision??"");let a;return n&&(o==="allow-once"||o==="allow-always")?a=o:n?a="allow":a="deny",s?this.handleAcpApprovalAction(s,a)?(this.callbacks.sendLocalActionResult(e.action_id,"ok"),{handled:!0,kind:"approval"}):(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"approval_not_found",`no pending approval for tool_call_id: ${s}`),{handled:!0,kind:"approval"}):(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"tool_call_id_required","tool_call_id is required"),{handled:!0,kind:"approval"})}return{handled:!1,kind:""}}async handleQuestionReplyEvent(e){let t;if(e.action)t={type:"action",value:e.action};else if(e.response?.type==="single")t={type:"text",value:e.response.value};else if(e.response?.type==="map")t={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:t})}async handleAcpQuestionReply(e){const t=await this.deliverQuestionReply(e.params??{});return t.delivered?(this.callbacks.sendLocalActionResult(e.action_id,"ok",t.result),{handled:!0,kind:t.kind??"question_reply"}):(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,t.errorCode,t.errorMsg),{handled:!0,kind:t.kind??"question_reply_failed"})}async deliverQuestionReply(e){const t=String(e.request_id??"").trim(),i=this.pendingQuestions.get(t);if(!t||!i||!this.acpClient)return{delivered:!1,errorCode:"interaction_request_not_pending",errorMsg:"The question is no longer pending; the reply was not delivered.",kind:"question_reply_not_pending"};const s=e.resolution&&typeof e.resolution=="object"?e.resolution:{},n=String(s.type??"");let o;if(n==="action"&&String(s.value??"")==="cancel")o=i.options.find(l=>/_skip$/.test(l.optionId)||l.kind.toLowerCase().includes("reject"));else{let l="";if(n==="text")l=String(s.value??"").trim();else if(n==="map"){const d=Array.isArray(s.entries)?s.entries:[];l=String(d[0]?.value??"").trim()}o=i.options.find(d=>d.name===l||d.optionId===l)}if(!o)return{delivered:!1,errorCode:"question_answer_invalid",errorMsg:"The selected answer does not match a pending question option.",kind:"question_reply_invalid"};try{await this.acpClient.respondPermissionOption(t,o.optionId)}catch(l){return{delivered:!1,errorCode:"question_reply_failed",errorMsg:l instanceof Error?l.message:String(l),kind:"question_reply_failed"}}this.pendingQuestions.delete(t),this.activeRun&&(this.activeRun.lastProgressAt=Date.now(),this.resetIdleTimer(this.activeRun));const a=/_skip$/.test(o.optionId);return{delivered:!0,result:{request_id:t,resolution:a?"cancel":"answer"},kind:a?"question_reply_cancel":"question_reply"}}resolveCwd(){if(this.currentAibotSessionId){const e=this.sessionBindings.get(this.currentAibotSessionId);if(e)return e}if(this.bindingStore&&this.currentAibotSessionId){const e=this.bindingStore.get(this.currentAibotSessionId);if(e?.cwd)return e.cwd}return process.cwd()}async bindSession(e,t){const i=this.sessionBindings.get(e);if(i)try{return await g.stat(i),!1}catch{r.info("acp-adapter",`Stale binding for session ${e}: ${i} no longer exists, allowing rebind to ${t}`),this.sessionBindings.delete(e)}return this.sessionBindings.set(e,t),this.bindingStore&&this.bindingStore.set(e,t),this.maybeRefreshKiroSkills(t),!this.sessionConnected&&this.agentProcess?.alive?this.connectSession(t).then(()=>!0).catch(s=>{r.error("acp-adapter",`Failed to create session on bind: ${I(s)}`);const n=this.buildToolbarContext("bind_failed",t),o=Array.isArray(n.available_models)&&n.available_models.length>0;return this.callbacks.sendUpdateBindingCard(e,"failed",t,o?n:void 0),!0}):(this.acpClient?.isAlive&&(this.bindingStore&&this.acpClient.sessionId&&this.bindingStore.setAcpSessionId(e,this.acpClient.sessionId),this.callbacks.sendUpdateBindingCard(e,"connected",t,this.buildToolbarContext("binding_ready",t))),Promise.resolve(!0))}getSessionCwd(e){return this.sessionBindings.get(e)}getSessionBindings(){return this.sessionBindings}maybeRefreshKiroSkills(e){if(this.config.command!=="kiro-cli"||!this.callbacks.onSkillsUpdate)return;const t=String(e??"").trim()||void 0;let i=[];try{i=O({mode:"kiro",projectDir:t})}catch(s){r.warn("acp-adapter",`Kiro skills scan failed: ${s instanceof Error?s.message:String(s)}`);return}try{this.callbacks.onSkillsUpdate(i,t)}catch(s){r.warn("acp-adapter",`onSkillsUpdate failed: ${s instanceof Error?s.message:String(s)}`)}}replayDeferredEvents(e){const t=this.deferredEvents.get(e);if(!t||t.length===0)return;if(this.activeRun){r.info("acp-adapter",`Cannot replay deferred events for session ${e}: agent busy`);return}const i=t.shift();i&&(t.length===0?(this.deferredEvents.delete(e),this.cancelDeferredTimer(e)):this.deferredEvents.set(e,t),r.info("acp-adapter",`Replaying deferred event ${i.event.event_id} for session ${e} (remaining: ${t.length})`),this.startRun(i.event,!1))}finishCompaction(e){if(!this.compacting)return;this.compacting=!1,this.compactingTimer&&(clearTimeout(this.compactingTimer),this.compactingTimer=null),this.suppressOrphanOutput(W),r.info("acp-adapter",`Compaction finished (${e}); replaying deferred events`),this.replayNextDeferredEvent();const t=this.compactionDoneResolver;this.compactionDoneResolver=null,t?.()}replayNextDeferredEvent(){if(this.activeRun||this.compacting)return;const e=this.deferredEvents.keys().next().value;e&&this.replayDeferredEvents(e)}announceDeferredComposing(e){}sendTerminalEventWithoutRun(e,t,i){this.emit("eventDone",e),this.callbacks.sendEventResult(e,t,i)}deliverInboundEvent(e){if(this.eventResults?.has(e.session_id,e.event_id)){const t=this.eventResults.get(e.session_id,e.event_id);r.info("acp-adapter",`Deduplicating event ${e.event_id} (cached: ${t.status})`),this.sendTerminalEventWithoutRun(e.event_id,t.status,t.msg);return}if(this.compacting){r.info("acp-adapter",`Event ${e.event_id} deferred: compaction in progress`),this.deferEvent(e);return}if(this.activeRun){r.info("acp-adapter",`Event ${e.event_id} rejected: busy`),this.sendTerminalEventWithoutRun(e.event_id,"failed","agent busy");return}if(!this.agentProcess?.alive){r.warn("acp-adapter",`Event ${e.event_id} cannot start: agent process not alive; requesting session recovery`),this.emit("internalError",{eventId:e.event_id,sessionId:e.session_id,errorMsg:"agent not alive",replayOriginalContent:!0});return}e.session_id&&e.session_id!==this.currentAibotSessionId&&(this.currentAibotSessionId=e.session_id),this.startRun(e,!1)}deliverStopEvent(e,t){this.acpClient?(this.acpClient.cancel().catch(()=>{}),this.flushStream()):this.flushStream(),this.activeRun?.eventId===e&&this.finishRun("canceled","stopped by user")}deferEvent(e){const t=this.deferredEvents.get(e.session_id)??[];t.some(i=>i.event.event_id===e.event_id)||(t.push({event:e,queuedAt:Date.now()}),this.deferredEvents.set(e.session_id,t),r.info("acp-adapter",`Deferred event ${e.event_id} for session ${e.session_id} (queue: ${t.length})`),this.scheduleDeferredTimeout(e.session_id))}deferredTimers=new Map;rejectDeferredEvents(e){for(const[t,i]of this.deferredEvents)for(const{event:s}of i)r.info("acp-adapter",`Rejecting deferred event ${s.event_id}: ${e}`),this.sendTerminalEventWithoutRun(s.event_id,"failed",e);this.deferredEvents.clear(),this.cancelAllDeferredTimers()}cancelDeferredTimer(e){const t=this.deferredTimers.get(e);t&&(clearTimeout(t),this.deferredTimers.delete(e))}cancelAllDeferredTimers(){for(const e of this.deferredTimers.values())clearTimeout(e);this.deferredTimers.clear()}scheduleDeferredTimeout(e){if(this.deferredTimers.has(e))return;const t=3e4,i=setTimeout(()=>{this.deferredTimers.delete(e);const s=this.deferredEvents.get(e);if(!(!s||s.length===0)&&!this.acpClient?.isAlive){r.error("acp-adapter",`Deferred events for session ${e} timed out after ${t/1e3}s (no ACP client)`),this.deferredEvents.delete(e);for(const{event:n}of s)this.sendTerminalEventWithoutRun(n.event_id,"failed","agent initialization timed out")}},t);this.deferredTimers.set(e,i)}startRun(e,t){if(!this.acpClient?.isAlive){this.deferEvent(e);return}const i=`acp_${++this.clientMsgSeq}_${Date.now()}`;this.activeRun={eventId:e.event_id,sessionId:e.session_id,threadId:e.thread_id,clientMsgIdBase:i,currentClientMsgId:i,currentSegmentIndex:0,chunkSeq:0,buffer:"",quotedStream:new C,markdownSegmenter:new re,quotedMessageId:void 0,flushTimer:null,idleTimer:null,awaitingToolResult:!1,responded:!1,silent:t,lastProgressAt:Date.now(),lastIdleCheckAt:Date.now(),idleNoProgressCount:0,consecutivePingFailures:0,lastToolProgress:new Map,lastToolProgressSentAt:new Map,toolNamesById:new Map,sentToolUseIds:new Set,lastToolUse:null,lastToolResult:null,busyRetryTimer:null,busyWaitSince:0,busyRetryCount:0,busyWaiting:!1};const s=this.activeRun;this.beginAuditCapture(e);const n=ae(e,this.resolveCwd());this.resetIdleTimer(s),n.modeId&&this.acpClient&&this.acpClient.setLiveMode(n.modeId).catch(()=>{}),n.modelId&&this.acpClient&&this.acpClient.setModel(n.modelId).catch(()=>{});const o=le(n.prompt,{messageId:e.msg_id,quotedMessageId:e.quoted_message_id}),a=this.injectQuoteProtocolOnce(s.sessionId,o),l=this.injectIdentityOnce(s.sessionId,a),d=this.injectSessionContextOnce(s.sessionId,l),h=this.injectRecoveryContext(s.sessionId,d);this.sendPromptWithRetry(s,h)}injectQuoteProtocolOnce(e,t){return!e||this.quoteProtocolTaughtSessions.has(e)?t:(this.quoteProtocolTaughtSessions.add(e),A(t,be))}injectIdentityOnce(e,t){if(!e||this.identityInjectedSessions.has(e))return t;const i=this.callbacks.getAgentProfile?.(),s=i?.agentName?.trim()??"",n=i?.introduction?.trim()??"";if(!s&&!n)return t;this.identityInjectedSessions.add(e);const o=["[system-identity]"];return s&&o.push(`Your name is "${s}".`),n&&o.push(`Your self-introduction: ${n}`),o.push("Treat this as an out-of-band instruction; do not echo or repeat it in your replies."),o.push("[/system-identity]"),r.info("acp-adapter",`Injected agent identity for session=${e} name="${s}"`),A(t,o.join(`
|
|
4
4
|
`))}injectSessionContextOnce(e,t){return!e||!this.sessionContextPending?t:(this.sessionContextPending=!1,r.info("acp-adapter",`Injected Grix session context for session=${e}`),A(t,V(e,this.callbacks.getAgentId?.())))}injectRecoveryContext(e,t){const i=this.recoveryContextBySessionId.get(e);return i?(this.recoveryContextBySessionId.delete(e),r.info("acp-adapter",`Injecting recovery context for session ${e} (chars=${i.length})`),A(t,`[recovery-context]
|
|
5
5
|
${i}
|
|
6
|
-
[/recovery-context]`)):t}sendPromptWithRetry(e,t){this.acpClient.send(t).then(()=>{this.activeRun===e&&(e.busyWaiting=!1)}).catch(i=>{if(we(i)){this.scheduleAgentBusyRetry(e,t,I(i));return}if(r.error("acp-adapter",`Prompt failed: ${I(i)}`),
|
|
6
|
+
[/recovery-context]`)):t}sendPromptWithRetry(e,t){this.acpClient.send(t).then(()=>{this.activeRun===e&&(e.busyWaiting=!1)}).catch(i=>{if(we(i)){this.scheduleAgentBusyRetry(e,t,I(i));return}if(r.error("acp-adapter",`Prompt failed: ${I(i)}`),_e(i)){const n=I(i),o=e.eventId,a=e.sessionId;r.info("acp-adapter",`Internal error escalated to bridge: event=${o} session=${a} err=${n}`),this.silentlyDiscardActiveRun(e),this.emit("internalError",{eventId:o,sessionId:a,errorMsg:n});return}const s=i instanceof Error?i.message:String(i);this.finishRun("failed",s)})}scheduleAgentBusyRetry(e,t,i){if(this.activeRun!==e)return;e.busyWaiting=!0;const s=Date.now();e.busyWaitSince||(e.busyWaitSince=s);const n=s-e.busyWaitSince;if(n>=L){r.error("acp-adapter",`agent busy wait exceeded ${Math.round(L/6e4)}min, giving up event=${e.eventId}: ${i}`),e.buffer+=`
|
|
7
7
|
|
|
8
|
-
The agent's own background turn kept the session busy past the wait limit, so this message was not delivered. Please resend it later.`,this.finishRun("failed");return}const o=Math.min(Ce*2**e.busyRetryCount,Ae);e.busyRetryCount+=1,r.info("acp-adapter",`agent busy, keeping event queued event=${e.eventId} waited=${Math.round(n/1e3)}s retry_in=${o}ms`),e.idleTimer&&(clearTimeout(e.idleTimer),e.idleTimer=null),e.lastProgressAt=s,e.busyRetryTimer=setTimeout(()=>{if(e.busyRetryTimer=null,this.activeRun===e){if(!this.acpClient?.isAlive){this.finishRun("failed","agent unreachable");return}this.resetIdleTimer(e),this.sendPromptWithRetry(e,t)}},o),e.busyRetryTimer.unref?.()}silentlyDiscardActiveRun(e){this.activeRun===e&&(this.snapshotAuditBoundary(e.eventId),this.activeRun=null,this.acpClient?.clearSettleTimer(),this.clearRunTimers(e),this.emit("eventDone",e.eventId))}async spawnProcess(){const e=[...this.config.args??[]];this.autoInjectArgs?.acp&&e.push("--acp"),this.autoInjectArgs?.model&&e.push("--model",this.autoInjectArgs.model),this.config.command==="gemini"&&!e.includes("--skip-trust")&&e.push("--skip-trust"),this.config.command.includes("qwen")&&!e.includes("--experimental-skills")&&e.push("--experimental-skills"),this.config.command==="kiro-cli"&&!e.includes("--trust-all-tools")&&e.push("--trust-all-tools"),(this.config.command==="copilot"||this.config.command.endsWith("/copilot")||this.config.command==="gh"&&e.includes("copilot"))&&(e.includes("--allow-all-tools")||e.push("--allow-all-tools"),e.includes("--allow-all-paths")||e.push("--allow-all-paths")),this.reasonixAuditTranscriptPath&&!e.includes("--transcript")&&(await g.mkdir(
|
|
8
|
+
The agent's own background turn kept the session busy past the wait limit, so this message was not delivered. Please resend it later.`,this.finishRun("failed");return}const o=Math.min(Ce*2**e.busyRetryCount,Ae);e.busyRetryCount+=1,r.info("acp-adapter",`agent busy, keeping event queued event=${e.eventId} waited=${Math.round(n/1e3)}s retry_in=${o}ms`),e.idleTimer&&(clearTimeout(e.idleTimer),e.idleTimer=null),e.lastProgressAt=s,e.busyRetryTimer=setTimeout(()=>{if(e.busyRetryTimer=null,this.activeRun===e){if(!this.acpClient?.isAlive){this.finishRun("failed","agent unreachable");return}this.resetIdleTimer(e),this.sendPromptWithRetry(e,t)}},o),e.busyRetryTimer.unref?.()}silentlyDiscardActiveRun(e){this.activeRun===e&&(this.snapshotAuditBoundary(e.eventId),this.activeRun=null,this.acpClient?.clearSettleTimer(),this.clearRunTimers(e),this.emit("eventDone",e.eventId))}async spawnProcess(){const e=[...this.config.args??[]];this.autoInjectArgs?.acp&&e.push("--acp"),this.autoInjectArgs?.model&&e.push("--model",this.autoInjectArgs.model),this.config.command==="gemini"&&!e.includes("--skip-trust")&&e.push("--skip-trust"),this.config.command.includes("qwen")&&!e.includes("--experimental-skills")&&e.push("--experimental-skills"),this.config.command==="kiro-cli"&&!e.includes("--trust-all-tools")&&e.push("--trust-all-tools"),(this.config.command==="copilot"||this.config.command.endsWith("/copilot")||this.config.command==="gh"&&e.includes("copilot"))&&(e.includes("--allow-all-tools")||e.push("--allow-all-tools"),e.includes("--allow-all-paths")||e.push("--allow-all-paths")),this.reasonixAuditTranscriptPath&&!e.includes("--transcript")&&(await g.mkdir(p.dirname(this.reasonixAuditTranscriptPath),{recursive:!0,mode:448}),await(await g.open(this.reasonixAuditTranscriptPath,"a",384)).close(),await g.chmod(this.reasonixAuditTranscriptPath,384),e.push("--transcript",this.reasonixAuditTranscriptPath)),this.agentProcess=new G(this.bridgeLog),this.baselineGroupPids=[];const i=this.resolveCwd();try{if(!(await g.stat(i)).isDirectory())throw new Error(`Bound path is not a directory: ${i}`)}catch(s){throw String(s?.code??"")==="ENOENT"?new Error(`Bound directory does not exist: ${i}. Please rebind with /grix open <valid-directory>.`):s}await Y(this.config.command,i,this.config.env,this.nativeProviderScope).catch(s=>{r.warn("acp-adapter",`native provider config skipped: ${s instanceof Error?s.message:String(s)}`)}),r.info("acp-adapter",`Spawning ACP agent command=${this.config.command} args=${JSON.stringify(e)} cwd=${i} envKeys=${JSON.stringify(Object.keys(this.config.env??{}).sort())}`);try{if(this.isKimi){const s=ee({...process.env,...this.config.env});r.info("acp-adapter",`Prepared Kimi Layer plugin at ${s.pluginRoot} (registrationChanged=${s.registrationChanged})`)}await this.agentProcess.start({command:this.config.command,args:e.length>0?e:void 0,cwd:i,env:this.config.env})}catch(s){throw r.error("acp-adapter",`Failed to spawn agent process: ${s}`),this.rejectDeferredEvents(`agent spawn failed: ${s instanceof Error?s.message:String(s)}`),this.emit("exit",null),s}r.info("acp-adapter","ACP agent process started"),this.agentProcess.on("error",s=>{this.stopped||(r.error("acp-adapter",`Agent process error: ${s.message}`),this.activeRun&&this.finishRun("failed",`agent process error: ${s.message}`),this.rejectDeferredEvents(`agent process error: ${s.message}`),this.emit("exit",null))}),this.agentProcess.on("exit",s=>{this.stopped||(r.error("acp-adapter",`Agent process exited unexpectedly (code=${s})`),this.activeRun&&this.finishRun("failed",`agent process exited (code=${s})`),this.rejectDeferredEvents(`agent process exited (code=${s})`),this.emit("exit",s))}),this.agentProcess.on("stderr",()=>{this.activeRun&&this.resetIdleTimer(this.activeRun)})}async connectSession(e){if(this.sessionConnected||!this.agentProcess?.transport)return;let t;this.acpMcpTools&&(t=await this.startInternalApiAndMcp(e||this.resolveCwd())),this.acpClient=new te,this.acpClient.on("event",l=>this.handleAcpEvent(l)),this.acpClient.on("activity",()=>{this.activeRun&&this.resetIdleTimer(this.activeRun)}),this.acpClient.on("session-lost",()=>{this.stopped||this.agentProcess?.alive&&(r.error("acp-adapter","ACP transport closed while agent process still alive, triggering respawn"),this.activeRun&&(this.callbacks.sendRunError(this.activeRun.eventId,this.activeRun.sessionId,"ACP transport closed"),this.finishRun("failed","ACP transport closed")),this.emit("exit",null))});const i=this.currentAibotSessionId&&this.bindingStore?this.bindingStore.getAcpSessionId(this.currentAibotSessionId):void 0,s=!!i&&i!=="__continue__",n=async l=>{await this.acpClient.connect({transport:this.agentProcess.transport,authMethod:this.acpAuthMethod,initialMode:this.acpInitialMode,initialModel:this.acpInitialModel,terminal:!0,cwd:e||this.resolveCwd(),mcpServers:t,sessionId:l})};let o=!1,a;try{await n(i)}catch(l){if(l instanceof ie){await this.handleAuthRequired(l);return}if(!i)throw this.handleConnectFailure(l),l;if(r.warn("acp-adapter",`Failed to load persisted session ${i}, fallback to session/new: ${I(l)}`),o=!0,a=i,this.config.command==="kiro-cli"&&this.currentAibotSessionId){r.info("acp-adapter",`Building kiro recovery summary: aibotSession=${this.currentAibotSessionId} acpSession=${i}`);const d=await this.buildKiroRecoveryContext(this.currentAibotSessionId,i,e||this.resolveCwd());d?(this.recoveryContextBySessionId.set(this.currentAibotSessionId,d),r.info("acp-adapter",`Recovery context prepared for session ${this.currentAibotSessionId} (chars=${d.length})`)):r.warn("acp-adapter",`Recovery context skipped: no summary generated for acpSession=${i}`)}await n(void 0)}this.sessionConnected=!0,this.sessionContextPending=!s||o,this.captureBaselineGroupPids(),r.info("acp-adapter",`ACP session ready: ${this.acpClient.sessionId}`),this.emit("acpSessionReady",this.acpClient.sessionId),this.currentAibotSessionId&&this.bindingStore&&this.bindingStore.setAcpSessionId(this.currentAibotSessionId,this.acpClient.sessionId);for(const[l,d]of this.sessionBindings){this.bindingStore&&this.acpClient.sessionId&&this.bindingStore.setAcpSessionId(l,this.acpClient.sessionId);const h=o?"binding_recreated":"binding_ready",u=this.buildToolbarContext(h,d);o&&a&&(u.previous_session_id=a,u.recreated_session_id=this.acpClient.sessionId),this.callbacks.sendUpdateBindingCard(l,"ready",d,u)}}async buildKiroRecoveryContext(e,t,i){try{const s=p.join(v(),".kiro","sessions","cli",`${t}.json`);await g.access(s),r.info("acp-adapter",`Reading kiro source session file: ${s}`);const n=await this.generateSummaryByKiroCli(s,i);if(!n)return;const o=p.join(v(),".grix","data","session-summaries");await g.mkdir(o,{recursive:!0});const a=p.join(o,`${e}.md`),l=[`SOURCE_KIRO_SESSION_FILE: ${s}`,"",n.trim(),""].join(`
|
|
9
9
|
`);return await g.writeFile(a,l,"utf8"),r.info("acp-adapter",`Recovery summary saved: ${a} (chars=${n.length})`),["[\u5386\u53F2\u4E0A\u4E0B\u6587\u56DE\u704C\u8BF4\u660E]","\u4EE5\u4E0B\u5185\u5BB9\u6765\u81EA\u65E7\u4F1A\u8BDD\u81EA\u52A8\u6458\u8981\uFF0C\u8BF7\u5728\u540E\u7EED\u56DE\u7B54\u4E2D\u5EF6\u7EED\u5176\u4E2D\u7684\u76EE\u6807\u3001\u7EA6\u675F\u548C\u7ED3\u8BBA\u3002",`SOURCE_KIRO_SESSION_FILE: ${s}`,`SUMMARY_FILE: ${a}`,"",n.trim()].join(`
|
|
10
10
|
`)}catch(s){r.warn("acp-adapter",`Failed to build kiro recovery context: ${I(s)}`);return}}async generateSummaryByKiroCli(e,t){const s=["chat","--no-interactive","--trust-tools=read,grep",["\u8BF7\u57FA\u4E8E\u4E0B\u9762\u5F15\u7528\u7684 Kiro \u539F\u59CB\u4F1A\u8BDD\u6587\u4EF6\u751F\u6210\u6458\u8981\uFF0C\u8F93\u51FA\u5FC5\u987B\u4E3A Markdown\u3002","\u8981\u6C42\uFF1A1) \u7B80\u660E\u51C6\u786E\uFF1B2) \u5305\u542B goals/constraints/decisions/open_items \u56DB\u90E8\u5206\uFF1B3) \u4E0D\u8981\u8F93\u51FA\u4EE3\u7801\u5757\u56F4\u680F\u3002",`\u4F1A\u8BDD\u6587\u4EF6\uFF1A@"${e}"`].join(`
|
|
11
11
|
`)],n=await this.runCommandCapture("kiro-cli",s,t,45e3);if(!n)return;const o=Re(n).trim(),a=o.indexOf("> "),l=a>=0?o.slice(a+2).trim():o;if(l)return r.info("acp-adapter",`Kiro summary generated from ${e} (raw_chars=${o.length}, body_chars=${l.length})`),l.split(`
|
|
12
12
|
`).filter(d=>!d.includes("Credits:")&&!d.includes("Time:")).join(`
|
|
13
|
-
`).trim()}runCommandCapture(e,t,i,s){return new Promise((n,o)=>{const a=E(e,t,{cwd:i,stdio:["ignore","pipe","pipe"],windowsHide:!0});let l="",d="";const
|
|
13
|
+
`).trim()}runCommandCapture(e,t,i,s){return new Promise((n,o)=>{const a=E(e,t,{cwd:i,stdio:["ignore","pipe","pipe"],windowsHide:!0});let l="",d="";const h=setTimeout(()=>{a.kill("SIGTERM"),o(new Error(`${e} timed out after ${s}ms`))},s);a.stdout.on("data",u=>{l+=String(u)}),a.stderr.on("data",u=>{d+=String(u)}),a.on("error",u=>{clearTimeout(h),o(u)}),a.on("close",u=>{if(clearTimeout(h),u===0){n(l||d);return}o(new Error(`${e} exited with code ${u}: ${d||l}`))})})}handleConnectFailure(e){r.error("acp-adapter",`ACP session creation failed: ${I(e)}`),this.acpClient?.dispose(),this.acpClient?.removeAllListeners(),this.acpClient=null,this.emit("exit",null)}async startInternalApiAndMcp(e=this.resolveCwd()){try{this.internalApi||(this.internalApi=new ne,this.internalApi.setInvokeHandler(async(a,l,d,h)=>this.callbacks.agentInvoke(a,l,h)),await this.internalApi.start(0),r.info("acp-adapter",`Internal API started at ${this.internalApi.url}`)),this.internalApi.setMcpBridgeUpHandler(a=>this.onMcpBridgeUp(a));const t=this.config.command??"",i=t.includes("qwen")?p.join(v(),".qwen","skills"):t.includes("gemini")?p.join(v(),".gemini","skills"):t.includes("kiro")?p.join(v(),".kiro","skills"):this.isReasonix?p.join(v(),".reasonix","skills"):this.isKimi?p.join(J({...process.env,...this.config.env}),"skills"):this.auditAgentType==="copilot"||/^copilot(?:\.cmd)?$/i.test(p.basename(t).toLowerCase())||/^gh(?:\.exe)?$/i.test(p.basename(t).toLowerCase())&&(this.config.args??[]).includes("copilot")?p.join(v(),".copilot","skills"):this.auditAgentType==="hermes"?p.join(this.config.env?.HERMES_HOME?.trim()||Z({...process.env,...this.config.env},this.nativeProviderScope),"skills"):void 0;if(i){const a=X(i);a.length>0&&r.info("acp-adapter",`Synced connector skills to ${i}: [${a.join(", ")}]`)}const s=p.resolve(w,"../../mcp/mcp-bridge-server.js"),n=this.internalApi.mcpBridgeWsUrl,o=p.resolve(w,"../../mcp/acp-mcp-server.js");if(this.isKimi){await this.internalApi.setCodebaseNavigatorCwd(e);const a={Authorization:`Bearer ${this.internalApi.token}`};return[{type:"http",name:"grix-app-bridge",url:this.internalApi.mcpBridgeHttpUrl,headers:a},{type:"http",name:"grix-connector-tools",url:this.internalApi.mcpToolsHttpUrl,headers:a},{type:"http",name:"codebase-navigator",url:this.internalApi.codebaseNavigatorHttpUrl,headers:a}]}return[{name:"grix-app-bridge",command:process.execPath,args:[s,"--ws-url",n],...!this.isReasonix&&{env:{GRIX_CONNECTOR_MCP_BRIDGE_WS:n}}},{name:"grix-connector-tools",command:process.execPath,args:[o,"--api-url",this.internalApi.url],...!this.isReasonix&&{env:{}}}]}catch(t){r.error("acp-adapter",`Failed to start MCP tools: ${t}`);return}}onMcpBridgeUp(e){this.callbacks.sendMcpFrame?.(e)}deliverMcpFrameToAgent(e){this.internalApi?.sendMcpFrameToBridge(e)}cleanup(){this.flushOrphanBuffer(),this.activeRun,this.pendingApprovals.clear(),this.pendingQuestions.clear(),this.pendingAutoCompact=!1,this.rejectDeferredEvents("adapter cleaned up"),this.cancelAllDeferredTimers(),this.sessionConnected=!1,this.acpClient&&(this.acpClient.dispose(),this.acpClient.removeAllListeners(),this.acpClient=null),this.agentProcess&&(this.agentProcess.removeAllListeners(),this.agentProcess=null),this.baselineGroupPids=[],this.terminalAuthChild&&(this.terminalAuthChild.kill("SIGKILL"),this.terminalAuthChild=null)}async handleAuthRequired(e){r.info("acp-adapter",`Auth required, methods: ${e.authMethods.map(s=>s.id).join(", ")}`);const t=e.authMethods.find(s=>/oauth|browser/i.test(s.id))??e.authMethods[0];if(!t)throw e;const i=this.currentAibotSessionId??"";this.callbacks.sendAuthNotification(i,`Authentication required (${t.id}). Initiating auth flow...`);for(const[s,n]of this.sessionBindings)this.callbacks.sendUpdateBindingCard(s,"failed",n);if(t.type==="terminal")await this.runTerminalAuth(t,i);else{const s=await this.captureAuthUrl();s&&this.callbacks.sendAuthNotification(i,`Please open this URL to authenticate:
|
|
14
14
|
${s}
|
|
15
15
|
|
|
16
|
-
Waiting for authentication to complete...`)}try{await this.acpClient.authenticate(t.id),r.info("acp-adapter","Authentication successful"),this.callbacks.sendAuthNotification(i,"Authentication successful. Resuming...");const s=this.acpMcpTools?await this.startInternalApiAndMcp():void 0;await this.acpClient.connect({transport:this.agentProcess.transport,initialMode:this.acpInitialMode,initialModel:this.acpInitialModel,cwd:this.resolveCwd(),mcpServers:s}),r.info("acp-adapter",`ACP session ready after auth: ${this.acpClient.sessionId}`),this.captureBaselineGroupPids(),this.emit("acpSessionReady",this.acpClient.sessionId);for(const[n,o]of this.sessionBindings)this.callbacks.sendUpdateBindingCard(n,"ready",o,this.buildToolbarContext("binding_ready",o))}catch(s){throw r.error("acp-adapter",`Auth retry failed: ${s}`),s}}runTerminalAuth(e,t){return new Promise((i,s)=>{const n=E(this.config.command,e.args??[],{env:{...process.env,...this.config.env??{}},stdio:["ignore","pipe","pipe"],windowsHide:!0});this.terminalAuthChild=n;const o=/https?:\/\/[^\s"')\]]+/;let a=!1,l=!1;const d=u=>{if(a)return;const y=u.toString().replace(/\x1b\[[0-9;]*m/g,"").match(o);y&&(a=!0,r.info("acp-adapter",`Terminal auth URL captured: ${y[0]}`),this.callbacks.sendAuthNotification(t,`Please open this URL on any device to authenticate:
|
|
16
|
+
Waiting for authentication to complete...`)}try{await this.acpClient.authenticate(t.id),r.info("acp-adapter","Authentication successful"),this.callbacks.sendAuthNotification(i,"Authentication successful. Resuming...");const s=this.acpMcpTools?await this.startInternalApiAndMcp():void 0;await this.acpClient.connect({transport:this.agentProcess.transport,initialMode:this.acpInitialMode,initialModel:this.acpInitialModel,terminal:!0,cwd:this.resolveCwd(),mcpServers:s}),r.info("acp-adapter",`ACP session ready after auth: ${this.acpClient.sessionId}`),this.captureBaselineGroupPids(),this.emit("acpSessionReady",this.acpClient.sessionId);for(const[n,o]of this.sessionBindings)this.callbacks.sendUpdateBindingCard(n,"ready",o,this.buildToolbarContext("binding_ready",o))}catch(s){throw r.error("acp-adapter",`Auth retry failed: ${s}`),s}}runTerminalAuth(e,t){return new Promise((i,s)=>{const n=E(this.config.command,e.args??[],{env:{...process.env,...this.config.env??{}},stdio:["ignore","pipe","pipe"],windowsHide:!0});this.terminalAuthChild=n;const o=/https?:\/\/[^\s"')\]]+/;let a=!1,l=!1;const d=u=>{if(a)return;const y=u.toString().replace(/\x1b\[[0-9;]*m/g,"").match(o);y&&(a=!0,r.info("acp-adapter",`Terminal auth URL captured: ${y[0]}`),this.callbacks.sendAuthNotification(t,`Please open this URL on any device to authenticate:
|
|
17
17
|
${y[0]}
|
|
18
18
|
|
|
19
|
-
Waiting for authentication to complete...`))};n.stdout?.on("data",d),n.stderr?.on("data",d);const p=setTimeout(()=>{l||(l=!0,n.kill("SIGKILL"),this.terminalAuthChild=null,s(new Error(`terminal auth timed out after ${U}ms`)))},U);n.on("error",u=>{l||(l=!0,clearTimeout(p),this.terminalAuthChild=null,s(u))}),n.on("exit",u=>{l||(l=!0,clearTimeout(p),this.terminalAuthChild=null,u===0?i():s(new Error(`terminal auth process exited with code ${u}`)))})})}captureAuthUrl(){return new Promise(e=>{if(!this.agentProcess){e(null);return}const t=/https?:\/\/[^\s"')\]]+/;let i=!1;const s=n=>{if(i)return;const a=n.toString().replace(/\x1b\[[0-9;]*m/g,"").match(t);a&&(i=!0,this.agentProcess.removeListener("stderr",s),e(a[0]))};this.agentProcess.on("stderr",s),setTimeout(()=>{i||(i=!0,this.agentProcess.removeListener("stderr",s),e(null))},3e4)})}handleAcpEvent(e){try{r.info("acp-adapter","incoming acp event",{eventType:e.type,sessionId:this.activeRun?.sessionId??null,eventKeys:Object.keys(e),rawEvent:x(e,2e3)})}catch{}if(e.type===f.PermissionRequest){this.activeRun&&this.resetIdleTimer(this.activeRun),this.handlePermissionRequest(e);return}if(e.type===f.ContextWindowUpdate){e.contextWindow&&this.callbacks.onContextWindowUpdated?.(e.contextWindow),this.compacting?this.finishCompaction("context-window-update"):e.contextWindow&&this.maybeScheduleAutoCompact(e.contextWindow);return}const t=this.activeRun;if(!t){this.handleOrphanAcpEvent(e);return}if(t.busyWaiting){this.handleOrphanAcpEvent(e);return}this.captureAuditEvent(e),t.responded||(t.responded=!0);let i=!1;switch(e.type){case f.Text:{if(t.lastToolUse=null,t.lastToolResult=null,e.content){i=!0;const s=t.quotedStream.consume(e.content);s.quotedMessageId&&(t.quotedMessageId=s.quotedMessageId),s.deltaContent&&this.appendToStream(t,s.deltaContent)}break}case f.ToolUse:{if(i=!0,t.awaitingToolResult=!0,t.lastToolResult=null,e.toolName){if(e.toolCallId&&(t.toolNamesById.set(e.toolCallId,e.toolName),t.sentToolUseIds.has(e.toolCallId)))break;const s=e.toolInput??"",n=S(s);if(t.lastToolUse?.toolName===e.toolName&&S(t.lastToolUse.toolInput)===n)break;t.lastToolUse={toolName:e.toolName,toolInput:s},e.toolCallId&&t.sentToolUseIds.add(e.toolCallId);const o=this.trimToolWirePayload?M(s):s;this.emitRawEventEnvelope(t,{type:"tool_use",payload:{tool_name:e.toolName,tool_input:o}})||this.callbacks.sendToolUse(t.eventId,t.sessionId,e.toolName,o)}break}case f.ToolResult:{t.awaitingToolResult=!1;{const s=e.toolCallId||e.toolName||"";t.lastToolProgress.delete(s),t.lastToolProgressSentAt.delete(s)}if(t.lastToolUse=null,e.content){i=!0;const s=N(t,e),n=e.content,o=S(n);if(t.lastToolResult?.toolName===s&&S(t.lastToolResult.content)===o)break;if(t.lastToolResult={toolName:s,content:n},!this.trimToolWirePayload||e.toolStatus==="failed"){const a=this.trimToolWirePayload?"(failed)":n;this.emitRawEventEnvelope(t,{type:"tool_result",payload:{tool_name:s,content:a}})||this.callbacks.sendToolResult(t.eventId,t.sessionId,s,a)}}break}case f.ToolProgress:{if(i=!0,e.content){const s=e.toolCallId||e.toolName||"";if(t.lastToolProgress.get(s)===e.content||(t.lastToolProgress.set(s,e.content),this.trimToolWirePayload))break;const n=Date.now(),o=t.lastToolProgressSentAt.get(s);if(o!==void 0&&n-o<ce)break;t.lastToolProgressSentAt.set(s,n);const a=N(t,e);this.emitRawEventEnvelope(t,{type:"tool_progress",payload:{tool_name:a,content:e.content}})||this.callbacks.sendToolResult(t.eventId,t.sessionId,a,e.content)}break}case f.Thinking:{t.lastToolUse=null,t.lastToolResult=null,e.content&&(i=!0,this.emitRawEventEnvelope(t,{type:"thinking",payload:{content:e.content}})||this.callbacks.sendThinking(t.eventId,t.sessionId,e.content));break}case f.Error:{t.lastToolUse=null,t.lastToolResult=null;const s=String(e.error??"unknown error");this.emitRawEventEnvelope(t,{type:"error",payload:{message:s}}),r.error("acp-adapter",`ACP error: ${s}`),this.callbacks.sendRunError(t.eventId,t.sessionId,s);break}case f.Result:{if(e.synthetic&&this.shouldDeferSyntheticResultForDetachedKimiTask(t,e)){r.info("acp-adapter",`Synthetic ACP Result deferred: kimi detached task still running event=${t.eventId} session=${t.sessionId}`),this.acpClient?.deferSettledResult();return}this.emitRawEventEnvelope(t,{type:"result",payload:{done:e.done??!0}});const s=t.quotedStream.flush();s.deltaContent&&this.appendToStream(t,s.deltaContent),s.quotedMessageId&&(t.quotedMessageId=s.quotedMessageId),this.flushStream(),this.finishRun("responded");break}}i&&(t.lastProgressAt=Date.now(),t.idleNoProgressCount=0),this.resetIdleTimer(t)}handlePermissionRequest(e){const t=e.permissionRequest;if(r.info("acp-adapter","permission_request received",{sessionId:this.activeRun?.sessionId,eventId:this.activeRun?.eventId,requestId:e.requestId??null,toolCallId:t?.toolCallId??null,toolName:t?.toolName??null,toolTitle:t?.toolTitle??null,optionsCount:Array.isArray(t?.options)?t.options.length:0,hasActiveRun:this.activeRun!==null,approvalMode:this.approvalMode,rawTransport:this.rawTransport,hasRawEventEnvelopeCb:typeof this.callbacks.sendRawEventEnvelope=="function",hasSendPermissionCardCb:typeof this.callbacks.sendPermissionCard=="function",rawPr:x(t,3e3)}),!t||!e.requestId||!this.acpClient){r.warn("acp-adapter","permission_request dropped: missing prerequisite",{hasPr:t!=null,hasRequestId:!!e.requestId,hasAcpClient:!!this.acpClient});return}const i=this.activeRun,s=ue(t);if(s&&i&&this.callbacks.sendAgentQuestionCard){i.idleTimer&&(clearTimeout(i.idleTimer),i.idleTimer=null),this.pendingQuestions.set(e.requestId,{options:[...t.options]}),this.callbacks.sendAgentQuestionCard(i.eventId,i.sessionId,{request_id:e.requestId,mode:"form",questions:[{header:s.prompt,prompt:s.prompt,options:s.answerOptions.map(a=>a.name)}]}),r.info("acp-adapter","AskUserQuestion forwarded via agent_question card",{sessionId:i.sessionId,eventId:i.eventId,requestId:e.requestId,optionsCount:s.answerOptions.length});return}if(this.approvalMode==="yolo"||this.approvalMode==="autoEdit"){r.info("acp-adapter",`Auto-approving (${this.approvalMode}): ${t.toolName}`),this.acpClient.respondPermission(t.requestId,{behavior:"allow"}).catch(()=>{});return}const n=t.toolCallId,o=de(t.rawParams,e.toolInput,t.toolTitle);this.pendingApprovals.set(n,e.requestId),i?(i.idleTimer&&(clearTimeout(i.idleTimer),i.idleTimer=null),this.emitRawEventEnvelope(i,{type:"permission_request",payload:{request_id:t.requestId,tool_call_id:n,tool_name:t.toolName,tool_title:t.toolTitle,...o?{tool_input:o}:{},options:t.options}})?r.info("acp-adapter","permission_request forwarded via rawEventEnvelope",{sessionId:i.sessionId,eventId:i.eventId,toolCallId:n,toolName:t.toolName}):(r.info("acp-adapter","permission_request falling back to sendPermissionCard",{sessionId:i.sessionId,eventId:i.eventId,toolCallId:n,toolName:t.toolName}),this.callbacks.sendPermissionCard({eventId:i.eventId,sessionId:i.sessionId,toolCallId:n,toolName:t.toolName,toolTitle:t.toolTitle,toolInput:o,options:t.options}))):(r.info("acp-adapter",`Permission request without active run, auto-approving: ${t.toolName}`),this.acpClient.respondPermission(e.requestId,{behavior:"allow"}),this.pendingApprovals.delete(n))}emitRawEventEnvelope(e,t){return!e||!this.rawTransport||!this.callbacks.sendRawEventEnvelope?!1:(this.callbacks.sendRawEventEnvelope(e.eventId,e.sessionId,{type:t.type,payload:t.payload,seq:++this.rawEventSeq,at:new Date().toISOString()}),!0)}resetIdleTimer(e,t){e.idleTimer&&clearTimeout(e.idleTimer);const i=t??(e.awaitingToolResult?ye:Ie),s=e.lastProgressAt+T-Date.now(),n=Math.max(0,Math.min(i,s));e.idleTimer=setTimeout(()=>this.onIdleTimeout(e),n)}onIdleTimeout(e){if(this.activeRun?.eventId!==e.eventId)return;const t=Date.now()-e.lastProgressAt;if(t>=T){r.error("acp-adapter",`Turn inactive ${Math.round(t/1e3)}s \u2265 cap ${Math.round(T/1e3)}s, forcing timeout: ${e.eventId}`),this.finishRun("failed",`turn inactivity timeout after ${Math.round(t/1e3)}s with no ACP activity`),this.declareStuck();return}const i=this.acpClient;i?.isAlive?i.ping(5e3).then(async s=>{if(this.activeRun?.eventId!==e.eventId)return;if(!s){await this.handlePingFailure(e,i);return}if(e.consecutivePingFailures=0,e.awaitingToolResult){const d=e.lastIdleCheckAt,p=await this.checkSessionFileActivity(i.sessionId,d);if(this.activeRun?.eventId!==e.eventId)return;e.lastIdleCheckAt=Date.now(),p?(e.lastProgressAt=Date.now(),r.info("acp-adapter",`Idle timer: tool running, ping ok, session file active, refreshing watchdog: ${e.eventId}`)):r.info("acp-adapter",`Idle timer: tool running, ping ok, continuing until inactivity cap: ${e.eventId}`),e.idleNoProgressCount=0,this.resetIdleTimer(e);return}const n=e.lastIdleCheckAt,o=e.lastProgressAt>n,a=o?!1:await this.checkSessionFileActivity(i.sessionId,n);if(this.activeRun?.eventId!==e.eventId)return;const l=o||a;e.lastIdleCheckAt=Date.now(),l?(a&&(r.info("acp-adapter",`Idle timer: no stream progress but session file active, continuing: ${e.eventId}`),e.lastProgressAt=Date.now()),e.idleNoProgressCount=0,this.resetIdleTimer(e)):(e.idleNoProgressCount++,r.info("acp-adapter",`Idle timer: ping ok, no stream progress (cycle=${e.idleNoProgressCount}), extending until inactivity cap (${Math.round(T/6e4)}min): ${e.eventId}`),this.resetIdleTimer(e))}).catch(()=>{this.activeRun?.eventId===e.eventId&&this.handlePingFailure(e,i)}):(r.error("acp-adapter",`Agent idle (no client), declaring stuck: ${e.eventId}`),this.finishRun("failed","agent unreachable"),this.declareStuck())}async handlePingFailure(e,t){const i=e.lastIdleCheckAt,s=e.lastProgressAt>i,n=s?!1:await this.checkSessionFileActivity(t.sessionId,i);if(this.activeRun?.eventId===e.eventId){if(s||n||e.awaitingToolResult){const o=s?"stream progress":n?"session file activity":"tool in flight";r.info("acp-adapter",`Idle timer: ping failed but ${o}, continuing: ${e.eventId}`),e.consecutivePingFailures=0,e.lastIdleCheckAt=Date.now(),n&&(e.lastProgressAt=Date.now()),this.resetIdleTimer(e);return}if(e.consecutivePingFailures++,e.lastIdleCheckAt=Date.now(),e.consecutivePingFailures<D){r.warn("acp-adapter",`Idle timer: ping failed (${e.consecutivePingFailures}/${D}), rechecking in ${F/1e3}s: ${e.eventId}`),this.resetIdleTimer(e,F);return}r.error("acp-adapter",`Ping failed ${e.consecutivePingFailures}x with no activity evidence, declaring stuck: ${e.eventId}`),this.finishRun("failed","agent unreachable"),this.declareStuck()}}async checkSessionFileActivity(e,t){if(!e)return!1;try{return this.isKimi?await pe(e,t):this.config.command==="kiro-cli"?await he(e,t):!1}catch{return!1}}shouldDeferSyntheticResultForDetachedKimiTask(e,t){if(!this.isKimi)return!1;try{return me(t.sessionId||this.acpClient?.sessionId||e.sessionId)}catch{return!1}}declareStuck(){this.acpClient&&(this.acpClient.clearSettleTimer(),this.acpClient.removeAllListeners(),this.acpClient=null),this.agentProcess&&(this.agentProcess.close().catch(()=>{}),this.agentProcess=null),this.emit("stuck")}appendToStream(e,t){e.buffer+=t,e.flushTimer||(e.flushTimer=setTimeout(()=>this.flushStream(),ve))}emitSegmentedStream(e,t){if(!t)return;const i=e.markdownSegmenter.push(t);for(const s of i)s.text&&(this.callbacks.sendStreamChunk(e.eventId,e.sessionId,s.text,++e.chunkSeq,s.closeAfter===!0,e.currentClientMsgId,e.quotedMessageId),s.closeAfter&&(e.currentSegmentIndex+=1,e.currentClientMsgId=`${e.clientMsgIdBase}_seg_${e.currentSegmentIndex}`,e.chunkSeq=0,r.info("acp-adapter",`stream segment rollover event=${e.eventId} segment=${e.currentSegmentIndex} reason=${s.reason??"threshold"}`)))}handleOrphanAcpEvent(e){if(!(!this.sessionConnected||this.compacting||Date.now()<this.orphanSuppressedUntil)){if(e.type===f.Text&&e.content){this.orphanSessionId===null&&(this.orphanSessionId=this.currentAibotSessionId??"");const t=this.orphanQuotedStream.consume(e.content);t.deltaContent&&(this.orphanBuffer+=t.deltaContent),this.orphanBuffer.length>=Te?this.flushOrphanBuffer():this.scheduleOrphanFlush();return}e.type===f.Result&&this.flushOrphanBuffer()}}scheduleOrphanFlush(){this.orphanFlushTimer&&clearTimeout(this.orphanFlushTimer),this.orphanFlushTimer=setTimeout(()=>{this.orphanFlushTimer=null,this.flushOrphanBuffer()},Se),this.orphanFlushTimer.unref?.()}flushOrphanBuffer(){if(this.orphanFlushTimer&&(clearTimeout(this.orphanFlushTimer),this.orphanFlushTimer=null),this.orphanSessionId===null&&!this.orphanBuffer)return;const e=this.orphanQuotedStream.flush();e.deltaContent&&(this.orphanBuffer+=e.deltaContent),this.orphanQuotedStream=new C;const t=this.orphanSessionId||this.currentAibotSessionId;this.orphanSessionId=null;const i=this.orphanBuffer.trim();if(this.orphanBuffer="",!!i){if(!t||!this.callbacks.sendAgentMessage){r.warn("acp-adapter",`orphan agent output dropped (${t?"no sendAgentMessage callback":"no session id"}) chars=${i.length}`);return}r.info("acp-adapter",`delivering orphan agent output session=${t} chars=${i.length}`);for(let s=0;s<i.length;s+=K)this.callbacks.sendAgentMessage(t,i.slice(s,s+K))}}suppressOrphanOutput(e){this.orphanSuppressedUntil=Date.now()+e,this.orphanFlushTimer&&(clearTimeout(this.orphanFlushTimer),this.orphanFlushTimer=null),this.orphanBuffer="",this.orphanQuotedStream=new C,this.orphanSessionId=null}flushStream(){const e=this.activeRun;if(!e||!e.buffer)return;e.flushTimer&&(clearTimeout(e.flushTimer),e.flushTimer=null);const t=e.buffer;e.buffer="",this.emitSegmentedStream(e,t)}finishRun(e,t){const i=this.activeRun;if(!i)return;this.snapshotAuditBoundary(i.eventId),this.activeRun=null,this.acpClient?.clearSettleTimer(),this.emit("eventDone",i.eventId),this.clearRunTimers(i);const s=i.quotedStream.flush();s.deltaContent&&(i.buffer+=s.deltaContent),s.quotedMessageId&&(i.quotedMessageId=s.quotedMessageId),t&&(e==="failed"?(r.error("acp-adapter",`finishRun failed: ${t} event=${i.eventId}`),i.buffer+=`
|
|
19
|
+
Waiting for authentication to complete...`))};n.stdout?.on("data",d),n.stderr?.on("data",d);const h=setTimeout(()=>{l||(l=!0,n.kill("SIGKILL"),this.terminalAuthChild=null,s(new Error(`terminal auth timed out after ${j}ms`)))},j);n.on("error",u=>{l||(l=!0,clearTimeout(h),this.terminalAuthChild=null,s(u))}),n.on("exit",u=>{l||(l=!0,clearTimeout(h),this.terminalAuthChild=null,u===0?i():s(new Error(`terminal auth process exited with code ${u}`)))})})}captureAuthUrl(){return new Promise(e=>{if(!this.agentProcess){e(null);return}const t=/https?:\/\/[^\s"')\]]+/;let i=!1;const s=n=>{if(i)return;const a=n.toString().replace(/\x1b\[[0-9;]*m/g,"").match(t);a&&(i=!0,this.agentProcess.removeListener("stderr",s),e(a[0]))};this.agentProcess.on("stderr",s),setTimeout(()=>{i||(i=!0,this.agentProcess.removeListener("stderr",s),e(null))},3e4)})}handleAcpEvent(e){try{r.info("acp-adapter","incoming acp event",{eventType:e.type,sessionId:this.activeRun?.sessionId??null,eventKeys:Object.keys(e),rawEvent:x(e,2e3)})}catch{}if(e.type===f.PermissionRequest){this.activeRun&&this.resetIdleTimer(this.activeRun),this.handlePermissionRequest(e);return}if(e.type===f.ContextWindowUpdate){e.contextWindow&&this.callbacks.onContextWindowUpdated?.(e.contextWindow),this.compacting?this.finishCompaction("context-window-update"):e.contextWindow&&this.maybeScheduleAutoCompact(e.contextWindow);return}const t=this.activeRun;if(!t){this.handleOrphanAcpEvent(e);return}if(t.busyWaiting){this.handleOrphanAcpEvent(e);return}this.captureAuditEvent(e),t.responded||(t.responded=!0);let i=!1;switch(e.type){case f.Text:{if(t.lastToolUse=null,t.lastToolResult=null,e.content){i=!0;const s=t.quotedStream.consume(e.content);s.quotedMessageId&&(t.quotedMessageId=s.quotedMessageId),s.deltaContent&&this.appendToStream(t,s.deltaContent)}break}case f.ToolUse:{if(i=!0,t.awaitingToolResult=!0,t.lastToolResult=null,e.toolName){if(e.toolCallId&&(t.toolNamesById.set(e.toolCallId,e.toolName),t.sentToolUseIds.has(e.toolCallId)))break;const s=e.toolInput??"",n=S(s);if(t.lastToolUse?.toolName===e.toolName&&S(t.lastToolUse.toolInput)===n)break;t.lastToolUse={toolName:e.toolName,toolInput:s},e.toolCallId&&t.sentToolUseIds.add(e.toolCallId);const o=this.trimToolWirePayload?M(s):s;this.emitRawEventEnvelope(t,{type:"tool_use",payload:{tool_name:e.toolName,tool_input:o}})||this.callbacks.sendToolUse(t.eventId,t.sessionId,e.toolName,o)}break}case f.ToolResult:{t.awaitingToolResult=!1;{const s=e.toolCallId||e.toolName||"";t.lastToolProgress.delete(s),t.lastToolProgressSentAt.delete(s)}if(t.lastToolUse=null,e.content){i=!0;const s=N(t,e),n=e.content,o=S(n);if(t.lastToolResult?.toolName===s&&S(t.lastToolResult.content)===o)break;if(t.lastToolResult={toolName:s,content:n},!this.trimToolWirePayload||e.toolStatus==="failed"){const a=this.trimToolWirePayload?"(failed)":n;this.emitRawEventEnvelope(t,{type:"tool_result",payload:{tool_name:s,content:a}})||this.callbacks.sendToolResult(t.eventId,t.sessionId,s,a)}}break}case f.ToolProgress:{if(i=!0,e.content){const s=e.toolCallId||e.toolName||"";if(t.lastToolProgress.get(s)===e.content||(t.lastToolProgress.set(s,e.content),this.trimToolWirePayload))break;const n=Date.now(),o=t.lastToolProgressSentAt.get(s);if(o!==void 0&&n-o<ce)break;t.lastToolProgressSentAt.set(s,n);const a=N(t,e);this.emitRawEventEnvelope(t,{type:"tool_progress",payload:{tool_name:a,content:e.content}})||this.callbacks.sendToolResult(t.eventId,t.sessionId,a,e.content)}break}case f.Thinking:{t.lastToolUse=null,t.lastToolResult=null,e.content&&(i=!0,this.emitRawEventEnvelope(t,{type:"thinking",payload:{content:e.content}})||this.callbacks.sendThinking(t.eventId,t.sessionId,e.content));break}case f.Error:{t.lastToolUse=null,t.lastToolResult=null;const s=String(e.error??"unknown error");this.emitRawEventEnvelope(t,{type:"error",payload:{message:s}}),r.error("acp-adapter",`ACP error: ${s}`),this.callbacks.sendRunError(t.eventId,t.sessionId,s);break}case f.Result:{if(e.synthetic&&this.shouldDeferSyntheticResultForDetachedKimiTask(t,e)){r.info("acp-adapter",`Synthetic ACP Result deferred: kimi detached task still running event=${t.eventId} session=${t.sessionId}`),this.acpClient?.deferSettledResult();return}this.emitRawEventEnvelope(t,{type:"result",payload:{done:e.done??!0}});const s=t.quotedStream.flush();s.deltaContent&&this.appendToStream(t,s.deltaContent),s.quotedMessageId&&(t.quotedMessageId=s.quotedMessageId),this.flushStream(),this.finishRun("responded");break}}i&&(t.lastProgressAt=Date.now(),t.idleNoProgressCount=0),this.resetIdleTimer(t)}handlePermissionRequest(e){const t=e.permissionRequest;if(r.info("acp-adapter","permission_request received",{sessionId:this.activeRun?.sessionId,eventId:this.activeRun?.eventId,requestId:e.requestId??null,toolCallId:t?.toolCallId??null,toolName:t?.toolName??null,toolTitle:t?.toolTitle??null,optionsCount:Array.isArray(t?.options)?t.options.length:0,hasActiveRun:this.activeRun!==null,approvalMode:this.approvalMode,rawTransport:this.rawTransport,hasRawEventEnvelopeCb:typeof this.callbacks.sendRawEventEnvelope=="function",hasSendPermissionCardCb:typeof this.callbacks.sendPermissionCard=="function",rawPr:x(t,3e3)}),!t||!e.requestId||!this.acpClient){r.warn("acp-adapter","permission_request dropped: missing prerequisite",{hasPr:t!=null,hasRequestId:!!e.requestId,hasAcpClient:!!this.acpClient});return}const i=this.activeRun,s=ue(t);if(s&&i&&this.callbacks.sendAgentQuestionCard){i.idleTimer&&(clearTimeout(i.idleTimer),i.idleTimer=null),this.pendingQuestions.set(e.requestId,{options:[...t.options]}),this.callbacks.sendAgentQuestionCard(i.eventId,i.sessionId,{request_id:e.requestId,mode:"form",questions:[{header:s.prompt,prompt:s.prompt,options:s.answerOptions.map(a=>a.name)}]}),r.info("acp-adapter","AskUserQuestion forwarded via agent_question card",{sessionId:i.sessionId,eventId:i.eventId,requestId:e.requestId,optionsCount:s.answerOptions.length});return}if(this.approvalMode==="yolo"||this.approvalMode==="autoEdit"){r.info("acp-adapter",`Auto-approving (${this.approvalMode}): ${t.toolName}`),this.acpClient.respondPermission(t.requestId,{behavior:"allow"}).catch(()=>{});return}const n=t.toolCallId,o=de(t.rawParams,e.toolInput,t.toolTitle);this.pendingApprovals.set(n,e.requestId),i?(i.idleTimer&&(clearTimeout(i.idleTimer),i.idleTimer=null),this.emitRawEventEnvelope(i,{type:"permission_request",payload:{request_id:t.requestId,tool_call_id:n,tool_name:t.toolName,tool_title:t.toolTitle,...o?{tool_input:o}:{},options:t.options}})?r.info("acp-adapter","permission_request forwarded via rawEventEnvelope",{sessionId:i.sessionId,eventId:i.eventId,toolCallId:n,toolName:t.toolName}):(r.info("acp-adapter","permission_request falling back to sendPermissionCard",{sessionId:i.sessionId,eventId:i.eventId,toolCallId:n,toolName:t.toolName}),this.callbacks.sendPermissionCard({eventId:i.eventId,sessionId:i.sessionId,toolCallId:n,toolName:t.toolName,toolTitle:t.toolTitle,toolInput:o,options:t.options}))):(r.info("acp-adapter",`Permission request without active run, auto-approving: ${t.toolName}`),this.acpClient.respondPermission(e.requestId,{behavior:"allow"}),this.pendingApprovals.delete(n))}emitRawEventEnvelope(e,t){return!e||!this.rawTransport||!this.callbacks.sendRawEventEnvelope?!1:(this.callbacks.sendRawEventEnvelope(e.eventId,e.sessionId,{type:t.type,payload:t.payload,seq:++this.rawEventSeq,at:new Date().toISOString()}),!0)}resetIdleTimer(e,t){e.idleTimer&&clearTimeout(e.idleTimer);const i=t??(e.awaitingToolResult?ye:Ie),s=e.lastProgressAt+T-Date.now(),n=Math.max(0,Math.min(i,s));e.idleTimer=setTimeout(()=>this.onIdleTimeout(e),n)}onIdleTimeout(e){if(this.activeRun?.eventId!==e.eventId)return;const t=Date.now()-e.lastProgressAt;if(t>=T){r.error("acp-adapter",`Turn inactive ${Math.round(t/1e3)}s \u2265 cap ${Math.round(T/1e3)}s, forcing timeout: ${e.eventId}`),this.finishRun("failed",`turn inactivity timeout after ${Math.round(t/1e3)}s with no ACP activity`),this.declareStuck();return}const i=this.acpClient;i?.isAlive?i.ping(5e3).then(async s=>{if(this.activeRun?.eventId!==e.eventId)return;if(!s){await this.handlePingFailure(e,i);return}if(e.consecutivePingFailures=0,e.awaitingToolResult){const d=e.lastIdleCheckAt,h=await this.checkSessionFileActivity(i.sessionId,d);if(this.activeRun?.eventId!==e.eventId)return;e.lastIdleCheckAt=Date.now(),h?(e.lastProgressAt=Date.now(),r.info("acp-adapter",`Idle timer: tool running, ping ok, session file active, refreshing watchdog: ${e.eventId}`)):r.info("acp-adapter",`Idle timer: tool running, ping ok, continuing until inactivity cap: ${e.eventId}`),e.idleNoProgressCount=0,this.resetIdleTimer(e);return}const n=e.lastIdleCheckAt,o=e.lastProgressAt>n,a=o?!1:await this.checkSessionFileActivity(i.sessionId,n);if(this.activeRun?.eventId!==e.eventId)return;const l=o||a;e.lastIdleCheckAt=Date.now(),l?(a&&(r.info("acp-adapter",`Idle timer: no stream progress but session file active, continuing: ${e.eventId}`),e.lastProgressAt=Date.now()),e.idleNoProgressCount=0,this.resetIdleTimer(e)):(e.idleNoProgressCount++,r.info("acp-adapter",`Idle timer: ping ok, no stream progress (cycle=${e.idleNoProgressCount}), extending until inactivity cap (${Math.round(T/6e4)}min): ${e.eventId}`),this.resetIdleTimer(e))}).catch(()=>{this.activeRun?.eventId===e.eventId&&this.handlePingFailure(e,i)}):(r.error("acp-adapter",`Agent idle (no client), declaring stuck: ${e.eventId}`),this.finishRun("failed","agent unreachable"),this.declareStuck())}async handlePingFailure(e,t){const i=e.lastIdleCheckAt,s=e.lastProgressAt>i,n=s?!1:await this.checkSessionFileActivity(t.sessionId,i);if(this.activeRun?.eventId===e.eventId){if(s||n||e.awaitingToolResult){const o=s?"stream progress":n?"session file activity":"tool in flight";r.info("acp-adapter",`Idle timer: ping failed but ${o}, continuing: ${e.eventId}`),e.consecutivePingFailures=0,e.lastIdleCheckAt=Date.now(),n&&(e.lastProgressAt=Date.now()),this.resetIdleTimer(e);return}if(e.consecutivePingFailures++,e.lastIdleCheckAt=Date.now(),e.consecutivePingFailures<D){r.warn("acp-adapter",`Idle timer: ping failed (${e.consecutivePingFailures}/${D}), rechecking in ${F/1e3}s: ${e.eventId}`),this.resetIdleTimer(e,F);return}r.error("acp-adapter",`Ping failed ${e.consecutivePingFailures}x with no activity evidence, declaring stuck: ${e.eventId}`),this.finishRun("failed","agent unreachable"),this.declareStuck()}}async checkSessionFileActivity(e,t){if(!e)return!1;try{return this.isKimi?await pe(e,t):this.config.command==="kiro-cli"?await he(e,t):!1}catch{return!1}}shouldDeferSyntheticResultForDetachedKimiTask(e,t){if(!this.isKimi)return!1;try{return me(t.sessionId||this.acpClient?.sessionId||e.sessionId)}catch{return!1}}declareStuck(){this.acpClient&&(this.acpClient.dispose(),this.acpClient.removeAllListeners(),this.acpClient=null),this.agentProcess&&(this.agentProcess.close().catch(()=>{}),this.agentProcess=null),this.emit("stuck")}appendToStream(e,t){e.buffer+=t,e.flushTimer||(e.flushTimer=setTimeout(()=>this.flushStream(),ve))}emitSegmentedStream(e,t){if(!t)return;const i=e.markdownSegmenter.push(t);for(const s of i)s.text&&(this.callbacks.sendStreamChunk(e.eventId,e.sessionId,s.text,++e.chunkSeq,s.closeAfter===!0,e.currentClientMsgId,e.quotedMessageId),s.closeAfter&&(e.currentSegmentIndex+=1,e.currentClientMsgId=`${e.clientMsgIdBase}_seg_${e.currentSegmentIndex}`,e.chunkSeq=0,r.info("acp-adapter",`stream segment rollover event=${e.eventId} segment=${e.currentSegmentIndex} reason=${s.reason??"threshold"}`)))}handleOrphanAcpEvent(e){if(!(!this.sessionConnected||this.compacting||Date.now()<this.orphanSuppressedUntil)){if(e.type===f.Text&&e.content){this.orphanSessionId===null&&(this.orphanSessionId=this.currentAibotSessionId??"");const t=this.orphanQuotedStream.consume(e.content);t.deltaContent&&(this.orphanBuffer+=t.deltaContent),this.orphanBuffer.length>=Te?this.flushOrphanBuffer():this.scheduleOrphanFlush();return}e.type===f.Result&&this.flushOrphanBuffer()}}scheduleOrphanFlush(){this.orphanFlushTimer&&clearTimeout(this.orphanFlushTimer),this.orphanFlushTimer=setTimeout(()=>{this.orphanFlushTimer=null,this.flushOrphanBuffer()},Se),this.orphanFlushTimer.unref?.()}flushOrphanBuffer(){if(this.orphanFlushTimer&&(clearTimeout(this.orphanFlushTimer),this.orphanFlushTimer=null),this.orphanSessionId===null&&!this.orphanBuffer)return;const e=this.orphanQuotedStream.flush();e.deltaContent&&(this.orphanBuffer+=e.deltaContent),this.orphanQuotedStream=new C;const t=this.orphanSessionId||this.currentAibotSessionId;this.orphanSessionId=null;const i=this.orphanBuffer.trim();if(this.orphanBuffer="",!!i){if(!t||!this.callbacks.sendAgentMessage){r.warn("acp-adapter",`orphan agent output dropped (${t?"no sendAgentMessage callback":"no session id"}) chars=${i.length}`);return}r.info("acp-adapter",`delivering orphan agent output session=${t} chars=${i.length}`);for(let s=0;s<i.length;s+=K)this.callbacks.sendAgentMessage(t,i.slice(s,s+K))}}suppressOrphanOutput(e){this.orphanSuppressedUntil=Date.now()+e,this.orphanFlushTimer&&(clearTimeout(this.orphanFlushTimer),this.orphanFlushTimer=null),this.orphanBuffer="",this.orphanQuotedStream=new C,this.orphanSessionId=null}flushStream(){const e=this.activeRun;if(!e||!e.buffer)return;e.flushTimer&&(clearTimeout(e.flushTimer),e.flushTimer=null);const t=e.buffer;e.buffer="",this.emitSegmentedStream(e,t)}finishRun(e,t){const i=this.activeRun;if(!i)return;this.snapshotAuditBoundary(i.eventId),this.activeRun=null,this.acpClient?.clearSettleTimer(),this.emit("eventDone",i.eventId),this.clearRunTimers(i);const s=i.quotedStream.flush();s.deltaContent&&(i.buffer+=s.deltaContent),s.quotedMessageId&&(i.quotedMessageId=s.quotedMessageId),t&&(e==="failed"?(r.error("acp-adapter",`finishRun failed: ${t} event=${i.eventId}`),i.buffer+=`
|
|
20
20
|
|
|
21
|
-
The task was interrupted. Please resend your instruction.`):r.info("acp-adapter",`finishRun ${e}: ${t} event=${i.eventId}`)),i.buffer&&(this.emitSegmentedStream(i,i.buffer),i.buffer="");const n=++i.chunkSeq;this.callbacks.sendFinalStreamChunkReliable?this.callbacks.sendFinalStreamChunkReliable(i.eventId,i.sessionId,i.currentClientMsgId).then(()=>{i.silent||this.callbacks.sendEventResult(i.eventId,e,t),this.persistEventResult(i,e,t),this.replayNextDeferredEvent(),this.tryRunPendingAutoCompact()}).catch(o=>{const a=`final output delivery failed: ${o instanceof Error?o.message:String(o)}`,l=e==="responded"?"failed":e,d=e==="responded"?a:t;r.warn("acp-adapter",`finalStreamChunk rejected/failed event=${i.eventId}: ${a}`),i.silent||this.callbacks.sendEventResult(i.eventId,l,d),this.persistEventResult(i,l,d),this.replayNextDeferredEvent(),this.tryRunPendingAutoCompact()}):(this.callbacks.sendStreamChunk(i.eventId,i.sessionId,"",n,!0,i.currentClientMsgId),i.silent||this.callbacks.sendEventResult(i.eventId,e,t),this.persistEventResult(i,e,t),this.replayNextDeferredEvent(),this.tryRunPendingAutoCompact())}beginAuditCapture(e){if(!e.audit?.enabled||this.auditCaptures.has(e.event_id))return;let t;if(this.reasonixAuditTranscriptPath)try{t=P(this.reasonixAuditTranscriptPath).size}catch{t=0}this.auditCaptures.set(e.event_id,{...t===void 0?{}:{transcriptStartOffset:t},events:[]}),this.activeAuditEventId=e.event_id}captureAuditEvent(e){if(this.isKimi)return;const t=this.activeRun?.eventId??this.activeAuditEventId;if(!t)return;const i=this.auditCaptures.get(t);i&&i.events.push({sequence:i.events.length,observedAt:new Date().toISOString(),event:structuredClone(e)})}snapshotAuditBoundary(e){const t=this.auditCaptures.get(e);if(!t)return;let i=t.transcriptStartOffset;if(this.reasonixAuditTranscriptPath)try{i=P(this.reasonixAuditTranscriptPath).size}catch{}this.completedAuditBoundaries.set(e,{adapterType:"acp",agentType:this.auditAgentType,cwd:this.resolveCwd(),...this.reasonixAuditTranscriptPath?{transcriptPath:this.reasonixAuditTranscriptPath,transcriptStartOffset:t.transcriptStartOffset??0,transcriptEndOffset:i??0}:{},providerSessionId:this.acpClient?.sessionId,...this.isKimi?{}:{acpEvidence:{events:t.events}}}),this.auditCaptures.delete(e),this.activeAuditEventId===e&&(this.activeAuditEventId=null)}getContextWindowUsedPercentage(e){return"usedPercentage"in e?Number.isFinite(e.usedPercentage)?e.usedPercentage:null:!Number.isFinite(e.used)||!Number.isFinite(e.size)||e.size<=0?null:Math.min(100,e.used/e.size*100)}maybeScheduleAutoCompact(e){const t=this.getContextWindowUsedPercentage(e);t===null||t<
|
|
21
|
+
The task was interrupted. Please resend your instruction.`):r.info("acp-adapter",`finishRun ${e}: ${t} event=${i.eventId}`)),i.buffer&&(this.emitSegmentedStream(i,i.buffer),i.buffer="");const n=++i.chunkSeq;this.callbacks.sendFinalStreamChunkReliable?this.callbacks.sendFinalStreamChunkReliable(i.eventId,i.sessionId,i.currentClientMsgId).then(()=>{i.silent||this.callbacks.sendEventResult(i.eventId,e,t),this.persistEventResult(i,e,t),this.replayNextDeferredEvent(),this.tryRunPendingAutoCompact()}).catch(o=>{const a=`final output delivery failed: ${o instanceof Error?o.message:String(o)}`,l=e==="responded"?"failed":e,d=e==="responded"?a:t;r.warn("acp-adapter",`finalStreamChunk rejected/failed event=${i.eventId}: ${a}`),i.silent||this.callbacks.sendEventResult(i.eventId,l,d),this.persistEventResult(i,l,d),this.replayNextDeferredEvent(),this.tryRunPendingAutoCompact()}):(this.callbacks.sendStreamChunk(i.eventId,i.sessionId,"",n,!0,i.currentClientMsgId),i.silent||this.callbacks.sendEventResult(i.eventId,e,t),this.persistEventResult(i,e,t),this.replayNextDeferredEvent(),this.tryRunPendingAutoCompact())}beginAuditCapture(e){if(!e.audit?.enabled||this.auditCaptures.has(e.event_id))return;let t;if(this.reasonixAuditTranscriptPath)try{t=P(this.reasonixAuditTranscriptPath).size}catch{t=0}this.auditCaptures.set(e.event_id,{...t===void 0?{}:{transcriptStartOffset:t},events:[]}),this.activeAuditEventId=e.event_id}captureAuditEvent(e){if(this.isKimi)return;const t=this.activeRun?.eventId??this.activeAuditEventId;if(!t)return;const i=this.auditCaptures.get(t);i&&i.events.push({sequence:i.events.length,observedAt:new Date().toISOString(),event:structuredClone(e)})}snapshotAuditBoundary(e){const t=this.auditCaptures.get(e);if(!t)return;let i=t.transcriptStartOffset;if(this.reasonixAuditTranscriptPath)try{i=P(this.reasonixAuditTranscriptPath).size}catch{}this.completedAuditBoundaries.set(e,{adapterType:"acp",agentType:this.auditAgentType,cwd:this.resolveCwd(),...this.reasonixAuditTranscriptPath?{transcriptPath:this.reasonixAuditTranscriptPath,transcriptStartOffset:t.transcriptStartOffset??0,transcriptEndOffset:i??0}:{},providerSessionId:this.acpClient?.sessionId,...this.isKimi?{}:{acpEvidence:{events:t.events}}}),this.auditCaptures.delete(e),this.activeAuditEventId===e&&(this.activeAuditEventId=null)}getContextWindowUsedPercentage(e){return"usedPercentage"in e?Number.isFinite(e.usedPercentage)?e.usedPercentage:null:!Number.isFinite(e.used)||!Number.isFinite(e.size)||e.size<=0?null:Math.min(100,e.used/e.size*100)}maybeScheduleAutoCompact(e){const t=this.getContextWindowUsedPercentage(e);t===null||t<U||this.pendingAutoCompact||this.compacting||(this.pendingAutoCompact=!0,r.info("acp-adapter",`[auto-compact] context_window usedPercentage=${t.toFixed(1)}% >= ${U}%, scheduling compact`),this.tryRunPendingAutoCompact())}tryRunPendingAutoCompact(){this.pendingAutoCompact&&(this.stopped||!this.acpClient?.isAlive||this.activeRun||this.compacting||(this.pendingAutoCompact=!1,this.execCommand("compact","",this.currentAibotSessionId??"").then(e=>{r.info("acp-adapter",`[auto-compact] compact done status=${e.status} msg=${e.message??""}`)}).catch(e=>{r.warn("acp-adapter",`[auto-compact] compact error: ${e instanceof Error?e.message:String(e)}`)})))}persistEventResult(e,t,i){this.eventResults&&!e.silent&&this.eventResults.set({sessionId:e.sessionId,eventId:e.eventId,status:t,msg:i,updatedAt:Date.now()})}}class ke extends ${adapterSessionId;constructor(e){super(),this.adapterSessionId=e}emitDone(e){this.emit("done",e)}emitError(e){if(this.listenerCount("error")===0){r.warn("acp-adapter",`Prompt handle error (no listeners): ${e.message}`);return}this.emit("error",e)}async cancel(){}}export{it as AcpAdapter};
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import{spawn as
|
|
2
|
-
`,"utf8"),
|
|
1
|
+
import{spawn as R}from"node:child_process";import{randomUUID as P}from"node:crypto";import{EventEmitter as T}from"node:events";import{existsSync as A,readFileSync as I,statSync as G,mkdirSync as $,writeFileSync as M,realpathSync as L}from"node:fs";import{homedir as _}from"node:os";import{join as m,dirname as O,resolve as j}from"node:path";import{fileURLToPath as D}from"node:url";import{log as a}from"../../core/log/index.js";import{killProcessGroup as y}from"../../core/runtime/spawn.js";import{InternalApiServer as B}from"../../core/mcp/internal-api-server.js";import{resolveMcpServerName as N,pruneLegacyMcpServerEntries as K}from"../../core/mcp/server-name.js";import{syncDefaultSkillsToDir as W}from"../../default-skills/index.js";import{buildSimpleProbeReport as Q}from"../shared/probe-util.js";import{getAgyModelCatalog as V,resolveAgyModelFlag as Y}from"./model-list.js";const c="agy-adapter";function q(g){try{return process.kill(g,0),!0}catch(t){return t.code==="EPERM"}}function F(g){try{return L(g)}catch{return g}}const k=m(_(),".gemini","antigravity-cli","log"),H="CLI store manager shutting down",U=5e3,w=15e3,J="30m",C=m(_(),".gemini","antigravity-cli","cache","last_conversations.json"),z=["\u4F60\u63A5\u5165\u7684\u662F Grix \u804A\u5929\u3002\u7528\u6237\u6D88\u606F\u88AB\u5305\u5728 <channel ...> \u6807\u7B7E\u91CC\uFF0C\u6807\u7B7E\u5C5E\u6027\u643A\u5E26\u672C\u8F6E\u4E0A\u4E0B\u6587\uFF1A","chat_id\uFF08\u5F53\u524D\u4F1A\u8BDD\uFF09\u3001agent_id\uFF08\u5F53\u524D Agent\uFF09\u3001event_id\uFF08\u672C\u8F6E\u4E8B\u4EF6\uFF09\u3001message_id\uFF08\u7528\u6237\u8FD9\u6761\u6D88\u606F\u7684 ID\uFF09\u3001user_id\uFF08\u53D1\u9001\u8005\uFF09\u3002","\u91CD\u8981\uFF1A\u4F60\u7684\u56DE\u590D\u5FC5\u987B\u8C03\u7528 grix \u7684 `grix_message_send` \u5DE5\u5177\u53D1\u9001\uFF08sessionId \u53D6\u81EA channel \u7684 chat_id\uFF0Ccontent \u4E3A\u4F60\u7684\u56DE\u590D\u5185\u5BB9\uFF09\uFF0C","\u4E0D\u8981\u53EA\u628A\u7B54\u6848\u6253\u5370\u5230\u6807\u51C6\u8F93\u51FA\u2014\u2014\u53EA\u6709\u8C03\u7528\u5DE5\u5177\u624D\u4F1A\u628A\u6D88\u606F\u771F\u6B63\u53D1\u7ED9\u7528\u6237\u3002","grix_message_send \u4F1A\u8FD4\u56DE\u4F60\u521A\u53D1\u51FA\u6D88\u606F\u7684 msg_id\u3002\u9700\u8981\u65F6\u4F60\u53EF\u4EE5\u7528\u8FD9\u4E2A ID \u505A\u540E\u7EED\u64CD\u4F5C\uFF0C\u4F8B\u5982\u8C03\u7528 grix_message_unsend\uFF08\u4F20 msgId\uFF09\u64A4\u56DE\u67D0\u6761\u6D88\u606F\u3002","\u8FDB\u7A0B\u5B89\u5168\uFF1A\u542F\u52A8\u4E34\u65F6\u6216\u6F14\u793A\u670D\u52A1\u65F6\u5FC5\u987B\u8BB0\u5F55\u5176 PID\uFF0C\u505C\u6B62\u65F6\u53EA\u7EC8\u6B62\u5DF2\u786E\u8BA4\u5C5E\u4E8E\u672C\u4EFB\u52A1\u7684\u8BE5 PID \u6216\u5176\u4E13\u5C5E\u8FDB\u7A0B\u7EC4\u3002","\u7981\u6B62\u4F7F\u7528 `pkill`\u3001`killall` \u6216\u5176\u4ED6\u6309\u540D\u79F0/\u547D\u4EE4\u884C\u6A21\u5F0F\u6279\u91CF\u6740\u8FDB\u7A0B\u7684\u547D\u4EE4\uFF1B\u7EDD\u4E0D\u80FD\u7EC8\u6B62 Grix MCP\u3001\u5F53\u524D agy \u6216\u5176\u7236\u8FDB\u7A0B\u3001connector \u6216\u5176\u4ED6\u65E0\u5173\u8FDB\u7A0B\u3002"].join(""),X=new Set(["send_msg","grix_reply"]),Z=new Set(["send_msg","grix_reply","delete_msg","session_send","grix_complete","grix_composing","grix_event_ack"]);function b(g){try{return structuredClone(g)}catch{return}}class gt extends T{type="agy";config;callbacks;runtimeResolver;alive=!1;stopped=!1;internalApi=null;activeEventId=null;activeEventSessionId=null;activeProcess=null;completedEventIds=new Set;activeEventSentViaTool=!1;auditCaptures=new Map;completedAuditBoundaries=new Map;watchdogPollTimer=null;watchdogGraceTimer=null;watchdogKilledActiveProcess=!1;sessionConversationMap=new Map;conversationOutputCache=new Map;eventQueue=[];systemInstructionInjectedSessions=new Set;identityInjectedSessions=new Set;constructor(t,e,i){super(),this.config=t,this.callbacks=e,this.runtimeResolver=i}async start(){this.alive||(this.alive=!0,this.stopped=!1,await this.startInternalApiAndInjectMcp(),a.info(c,"AgyAdapter started"))}async startInternalApiAndInjectMcp(){try{this.internalApi=new B,this.internalApi.setInvokeHandler(async(n,d,h,p)=>this.handleToolInvoke(n,d,p)),await this.internalApi.start(0),a.info(c,`Internal API started at ${this.internalApi.url}`);const t=this.getMcpConfig(),e=m(_(),".gemini","config","mcp_config.json");$(O(e),{recursive:!0});let i={};try{A(e)&&(i=JSON.parse(I(e,"utf8")))}catch{}const s=i.mcpServers&&typeof i.mcpServers=="object"?i.mcpServers:{};K(s,this.internalApi.url),s[t.name]={command:t.command,args:t.args},i.mcpServers=s,M(e,`${JSON.stringify(i,null,2)}
|
|
2
|
+
`,"utf8"),a.info(c,`MCP config injected into ${e}`);const r=m(_(),".gemini","skills"),o=W(r);o.length>0&&a.info(c,`Synced connector skills to ${r}: [${o.join(", ")}]`)}catch(t){a.warn(c,`Failed to start MCP tools (non-fatal): ${t instanceof Error?t.message:String(t)}`)}}async handleToolInvoke(t,e,i){let s=e;Z.has(t)&&this.activeEventSessionId&&(s.session_id==null||s.session_id==="")&&(s={...s,session_id:this.activeEventSessionId});const r=this.activeEventId?this.auditCaptures.get(this.activeEventId):void 0,o=r?{sequence:r.toolInvocations.length,observedAt:new Date().toISOString(),action:t,params:b(s)}:void 0;r&&o&&r.toolInvocations.push(o);try{const n=await this.callbacks.agentInvoke(t,s,i);return o&&(o.completedAt=new Date().toISOString(),o.result=b(n)),X.has(t)&&this.activeEventId&&(this.activeEventSentViaTool=!0),n}catch(n){throw o&&(o.completedAt=new Date().toISOString(),o.error=n instanceof Error?n.message:String(n)),n}}async stop(){if(!this.stopped){if(this.stopped=!0,this.alive=!1,this.stopTurnWatchdog(),this.watchdogKilledActiveProcess=!1,this.activeProcess){try{y(this.activeProcess,"SIGKILL")}catch{}this.activeProcess=null}if(this.activeEventId&&this.activeEventSessionId)try{this.callbacks.sendEventResult(this.activeEventId,"canceled","adapter stopped")}catch{}if(this.activeEventId&&this.snapshotAuditBoundary(this.activeEventId),this.activeEventId=null,this.activeEventSessionId=null,this.internalApi){try{await this.internalApi.stop()}catch{}this.internalApi=null}this.emit("exit",0),a.info(c,"AgyAdapter stopped")}}isAlive(){return this.alive}async createSession(t){return t.cwd??P()}async resumeSession(t,e){}async destroySession(t){this.systemInstructionInjectedSessions.delete(t),this.identityInjectedSessions.delete(t)}onAgentProfileChanged(){this.identityInjectedSessions.clear()}sendPrompt(t){const e=new T;return e.adapterSessionId=t.adapterSessionId,e.cancel=async()=>{e.emit("done",{status:"canceled"})},e}async cancel(t){}cancelCurrentRun(){if(!this.activeEventId||!this.activeProcess)return;const t=this.activeEventId;a.info(c,`cancelCurrentRun: killing process group for event ${t}`);try{y(this.activeProcess,"SIGKILL")}catch{}this.activeProcess=null,this.callbacks.sendEventResult(t,"canceled","restarted by user"),this.clearActiveEvent(t),this.drainQueue()}deliverInboundEvent(t){if(!this.stopped){if(this.completedEventIds.has(t.event_id)){a.debug(c,`Skipping duplicate event: ${t.event_id}`);return}if(this.callbacks.sendEventAck(t.event_id,t.session_id),this.activeEventId){a.info(c,`Queuing event ${t.event_id} (active: ${this.activeEventId})`),this.eventQueue.push(t);return}this.processEvent(t)}}deliverStopEvent(t,e){if(this.activeEventId===t&&this.activeProcess){a.info(c,`Stopping event ${t}`);try{y(this.activeProcess,"SIGKILL")}catch{}this.activeProcess=null,this.callbacks.sendEventResult(t,"canceled","stopped by user"),this.clearActiveEvent(t),this.drainQueue()}}setPermissionHandler(){}async ping(t){if(this.stopped||!this.alive||!this.internalApi)return!1;const e=this.activeProcess;if(!e)return!0;if(!e.pid||!q(e.pid))return!1;const i=!!(e.stdout&&!e.stdout.destroyed),s=!!(e.stderr&&!e.stderr.destroyed);return i&&s}getStatus(){return{alive:this.alive,busy:this.activeEventId!==null,sessions:this.sessionConversationMap.size}}async probe(t){const e=this.getStatus();return Q(this.config.command||"agy",{alive:e.alive,busy:e.busy,started:e.alive},t)}getActiveEventIds(){return this.activeEventId?[this.activeEventId]:[]}takeAuditBoundary(t){const e=this.completedAuditBoundaries.get(t);return e&&this.completedAuditBoundaries.delete(t),e}snapshotAuditBoundary(t){const e=this.auditCaptures.get(t);if(!e)return;this.auditCaptures.delete(t);let i;if(e.logPath)try{i=G(e.logPath).size}catch{}const s=this.sessionConversationMap.get(e.sessionId)??e.conversationId;this.completedAuditBoundaries.set(t,{adapterType:"agy",...s?{providerSessionId:s}:{},...e.cwd?{cwd:e.cwd}:{},...e.logPath?{transcriptPath:e.logPath,transcriptStartOffset:e.logStartOffset??0,transcriptEndOffset:i??0}:{},agyEvidence:{...e.prompt===void 0?{}:{prompt:e.prompt},...e.stdout===void 0?{}:{stdout:e.stdout},...e.stderr===void 0?{}:{stderr:e.stderr},...e.exitCode===void 0?{}:{exitCode:e.exitCode},...e.response===void 0?{}:{response:e.response},toolInvocations:e.toolInvocations}})}clearActiveEventForShutdown(){if(this.activeProcess)try{y(this.activeProcess,"SIGKILL")}catch{}this.activeEventId&&this.snapshotAuditBoundary(this.activeEventId),this.stopTurnWatchdog(),this.watchdogKilledActiveProcess=!1,this.activeEventId=null,this.activeEventSessionId=null,this.activeProcess=null,this.activeEventSentViaTool=!1}getMcpConfig(){if(!this.internalApi)return null;const t=j(D(import.meta.url),"../../../mcp/acp-mcp-server.js");return{name:N(this.callbacks.getAgentId?.()),command:process.execPath,args:[t,"--api-url",this.internalApi.url]}}processEvent(t){const{event_id:e,session_id:i}=t,s=this.runtimeResolver(i);if(!s.cwd){a.warn(c,`No working directory for session ${i}, event should have been intercepted by bridge`),this.callbacks.forceCompleteInternalEvent(e,i);return}this.activeEventId=e,this.activeEventSessionId=i,this.activeEventSentViaTool=!1,this.emit("eventStarted",e,i),a.info(c,`Processing event ${e} for session ${i}`);const r=this.buildAgyPrompt(t);t.audit?.enabled===!0&&this.auditCaptures.set(e,{sessionId:i,...s.cwd?{cwd:s.cwd}:{},...s.conversationId?{conversationId:s.conversationId}:{},prompt:r,toolInvocations:[]}),this.spawnAgyPrint(e,i,r,s)}buildAgyPrompt(t){const i=[["chat_id",t.session_id],["agent_id",this.callbacks.getAgentId?.()],["event_id",t.event_id],["message_id",t.msg_id],["user_id",t.sender_id],["quoted_message_id",t.quoted_message_id]].filter(([,d])=>d!=null&&d!=="").map(([d,h])=>`${d}="${h}"`).join(" "),s=this.renderContextBlock(t),r=s?`${t.content}
|
|
3
3
|
|
|
4
4
|
[Conversation context]
|
|
5
5
|
${s}`:t.content,o=`<channel source="grix-agy" ${i}>
|
|
6
6
|
${r}
|
|
7
7
|
</channel>`,n=[];if((!t.session_id||!this.systemInstructionInjectedSessions.has(t.session_id))&&(t.session_id&&this.systemInstructionInjectedSessions.add(t.session_id),n.push(`[grix-system-instructions]
|
|
8
|
-
${
|
|
9
|
-
[/grix-system-instructions]`)),!t.session_id||!this.identityInjectedSessions.has(t.session_id)){const
|
|
8
|
+
${z}
|
|
9
|
+
[/grix-system-instructions]`)),!t.session_id||!this.identityInjectedSessions.has(t.session_id)){const d=this.buildIdentityBlock().trim();d&&(t.session_id&&this.identityInjectedSessions.add(t.session_id),n.push(d))}return n.length>0?`${o}
|
|
10
10
|
|
|
11
11
|
${n.join(`
|
|
12
12
|
|
|
13
13
|
`)}`:o}buildIdentityBlock(){const t=this.callbacks.getAgentProfile?.(),e=t?.agentName?.trim()??"",i=t?.introduction?.trim()??"";if(!e&&!i)return"";const s=["[system-identity]"];return e&&s.push(`\u4F60\u7684\u540D\u5B57\u662F"${e}"\u3002`),i&&s.push(`\u4F60\u7684\u81EA\u6211\u4ECB\u7ECD\uFF1A${i}`),s.push("\u8FD9\u662F\u5E26\u5916\u6307\u4EE4\uFF1B\u4E0D\u8981\u5728\u56DE\u590D\u4E2D\u590D\u8FF0\u6216\u5F15\u7528\u672C\u6BB5\u3002"),s.push("[/system-identity]"),`${s.join(`
|
|
14
14
|
`)}
|
|
15
15
|
|
|
16
|
-
`}renderContextBlock(t){const e=t.context_messages_json;if(!e)return"";let i;try{const n=JSON.parse(e);if(!Array.isArray(n))return"";i=n}catch{return""}const s=String(t.session_type??"")==="2",r=String(t.msg_id??""),o=[];for(const n of i){const
|
|
17
|
-
`)}spawnAgyPrint(t,e,i,s){const r=this.buildPrintArgs(i,s),
|
|
18
|
-
`).filter(
|
|
16
|
+
`}renderContextBlock(t){const e=t.context_messages_json;if(!e)return"";let i;try{const n=JSON.parse(e);if(!Array.isArray(n))return"";i=n}catch{return""}const s=String(t.session_type??"")==="2",r=String(t.msg_id??""),o=[];for(const n of i){const d=String(n?.msg_id??"");if(d&&d===r)continue;const h=String(n?.content??"").trim();if(!h)continue;const p=String(n?.sender_id??"");if(h.startsWith("[\u5F15\u7528\u6D88\u606F]")){const f=h.slice(6).replace(/^\s*\n?/,"").trim();o.push(s&&p?`[\u5F15\u7528\u6D88\u606F] (\u6765\u81EA ${p})\uFF1A${f}`:`[\u5F15\u7528\u6D88\u606F]\uFF1A${f}`)}else o.push(s&&p?`[${p}]\uFF1A${h}`:h)}return o.join(`
|
|
17
|
+
`)}spawnAgyPrint(t,e,i,s){try{$(k,{recursive:!0})}catch(l){const u=l instanceof Error?l.message:String(l),S=`unable to prepare agy log directory: ${u}`;a.error(c,`Log setup failed for event ${t}: ${u}`),this.handleProcessResult(t,e,"",S,s);return}const r=m(k,`grix-${P()}.log`),o=this.buildPrintArgs(i,s,r),n={...process.env,...this.config.env??{}};a.info(c,`Spawning: agy ${o.map(l=>l.includes(" ")?`"${l}"`:l).join(" ")}`);const d=R(this.config.command,o,{cwd:s.cwd,env:n,stdio:["pipe","pipe","pipe"],detached:process.platform!=="win32"});this.activeProcess=d;const h=[],p=[];d.stdout?.on("data",l=>{h.push(l)}),d.stderr?.on("data",l=>{p.push(l)}),d.on("error",l=>{a.error(c,`Process spawn error for event ${t}: ${l.message}`),this.handleProcessResult(t,e,"",`spawn error: ${l.message}`,s)}),d.on("close",l=>{if(a.info(c,`Process exited for event ${t} with code ${l}`),this.activeEventId!==t)return;this.stopTurnWatchdog();const u=Buffer.concat(h).toString("utf-8"),S=Buffer.concat(p).toString("utf-8"),E=this.auditCaptures.get(t);E&&(E.stdout=u,E.stderr=S,E.exitCode=l);const x=this.watchdogKilledActiveProcess&&l===null;if(this.watchdogKilledActiveProcess=!1,x)u.trim()||this.activeEventSentViaTool?this.handleProcessResult(t,e,u,"",s):setTimeout(()=>{const v=this.extractLogError(r);this.handleProcessResult(t,e,u,v,s)},300);else if(l!==0){const v=S.trim()||`process exited with code ${l}`;this.handleProcessResult(t,e,u,v,s)}else u.trim()?this.handleProcessResult(t,e,u,"",s):this.activeEventSentViaTool?this.handleProcessResult(t,e,u,"",s):setTimeout(()=>{const v=this.extractLogError(r);this.handleProcessResult(t,e,u,v,s)},300)}),d.stdin?.end();const f=this.auditCaptures.get(t);f&&(f.logPath=r,f.logStartOffset=0),this.startTurnWatchdog(t,r)}startTurnWatchdog(t,e){this.stopTurnWatchdog(),this.watchdogPollTimer=setInterval(()=>{if(this.activeEventId!==t||!this.activeProcess){this.stopTurnWatchdog();return}this.watchLogHasTurnEndMarker(e)&&(this.watchdogPollTimer&&(clearInterval(this.watchdogPollTimer),this.watchdogPollTimer=null),a.warn(c,`Turn end marker logged for event ${t} but process still alive; grace ${w}ms before kill`),this.watchdogGraceTimer=setTimeout(()=>{if(this.watchdogGraceTimer=null,!(this.activeEventId!==t||!this.activeProcess)){a.warn(c,`agy process still alive ${w}ms after turn end (shutdown deadlock); killing process group for event ${t}`),this.watchdogKilledActiveProcess=!0;try{y(this.activeProcess,"SIGKILL")}catch{}}},w),this.watchdogGraceTimer.unref())},U),this.watchdogPollTimer.unref()}stopTurnWatchdog(){this.watchdogPollTimer&&(clearInterval(this.watchdogPollTimer),this.watchdogPollTimer=null),this.watchdogGraceTimer&&(clearTimeout(this.watchdogGraceTimer),this.watchdogGraceTimer=null)}watchLogHasTurnEndMarker(t){try{return I(t,"utf-8").includes(H)}catch{return!1}}buildPrintArgs(t,e,i){const s=["--print-timeout",J,"--log-file",i],r=e.modelId?Y(e.modelId,V()):"";return r&&s.push("--model",r),e.cwd&&s.push("--add-dir",e.cwd),s.push("--dangerously-skip-permissions"),e.conversationId&&s.push("--conversation",e.conversationId),s.push("-p",t),s}readAgyConversationId(t){try{if(A(C)){const e=JSON.parse(I(C,"utf-8"));return e[F(t)]||e[t]||void 0}}catch(e){a.debug(c,`readAgyConversationId: ${e}`)}}extractLogError(t){try{if(!A(t))return a.info(c,`extractLogError: log file not found: ${t}`),"agy completed without output";const e=I(t,"utf-8");if(!e)return a.info(c,`extractLogError: log file is empty: ${t}`),"agy completed without output";const i=e.split(`
|
|
18
|
+
`).filter(s=>s.startsWith("E")).map(s=>{const r=s.indexOf("] ");return r>=0?s.slice(r+2):s}).filter(s=>s.length>0);if(i.length>0){const s=i[i.length-1];return a.info(c,`Extracted error from agy log: ${s.slice(0,120)}`),s.length>500?s.slice(0,500)+"...":s}return"agy completed without output (possible auth or quota issue)"}catch(e){return a.info(c,`extractLogError: ${e}`),"agy completed without output"}}handleProcessResult(t,e,i,s,r){if(this.activeEventId===t){if(s&&!i.trim()){a.error(c,`Event ${t} failed: ${s}`);const n=s.includes("RESOURCE_EXHAUSTED")||s.includes("quota")?"The agy API quota has been exhausted. Please try again later.":`agy execution failed: ${s}`;this.callbacks.sendStreamChunk(t,e,n,1,!0,`agy-${t}`),this.callbacks.sendEventResult(t,"failed",s)}else{const o=i.trim();if(o){const n=r.cwd,d=n?this.extractDelta(o,n):o;n&&this.conversationOutputCache.set(n,o);const h=this.auditCaptures.get(t);h&&(h.response=d),d&&!this.activeEventSentViaTool&&this.callbacks.sendStreamChunk(t,e,d,1,!0,`agy-${t}`)}if(r.cwd){const n=this.readAgyConversationId(r.cwd);n&&n!==r.conversationId&&(this.sessionConversationMap.set(e,n),this.callbacks.persistConversationId(e,n))}this.callbacks.sendEventResult(t,"responded")}this.clearActiveEvent(t),this.drainQueue()}}extractDelta(t,e){const i=this.conversationOutputCache.get(e);return i&&t.startsWith(i)?t.slice(i.length).trim():t}clearActiveEvent(t){const e=t??this.activeEventId;if(e){if(this.completedEventIds.add(e),this.completedEventIds.size>1e3){const i=Array.from(this.completedEventIds);this.completedEventIds.clear();for(let s=500;s<i.length;s++)this.completedEventIds.add(i[s])}this.snapshotAuditBoundary(e)}this.activeEventId=null,this.activeEventSessionId=null,this.activeProcess=null,this.activeEventSentViaTool=!1,this.stopTurnWatchdog(),this.watchdogKilledActiveProcess=!1,e&&this.emit("eventDone",e)}drainQueue(){if(this.stopped||this.activeEventId)return;const t=this.eventQueue.shift();t&&(a.info(c,`Draining queued event ${t.event_id}`),this.processEvent(t))}}export{gt as AgyAdapter};
|
|
Binary file
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"bridgeVersion": "4.0.
|
|
4
|
-
"tarball": "grix-dsh-bridge-4.0.
|
|
5
|
-
"size":
|
|
3
|
+
"bridgeVersion": "4.0.2",
|
|
4
|
+
"tarball": "grix-dsh-bridge-4.0.2.tgz",
|
|
5
|
+
"size": 22009,
|
|
6
6
|
"unpackedSize": 87631,
|
|
7
|
-
"shasum": "
|
|
8
|
-
"integrity": "sha512-
|
|
7
|
+
"shasum": "71ff5ab505ab6879d00d62571235dbca68276b59",
|
|
8
|
+
"integrity": "sha512-GC7a/e0X1Thaj5kUmjx00/Fbf8cS0klllCrrekV0wcF4xYD1lyDmY/Xaktxp9SyNN6G7diGeu4/hne03x/qyDQ=="
|
|
9
9
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import J from"node:http";import{randomBytes as E}from"node:crypto";import{log as c}from"../log/index.js";import{PHASE1_INVOKE_ACTIONS as M,TOOLS as S,toolCallToInvoke as P}from"./tools.js";import{WebSocketServer as R,WebSocket as _}from"ws";const w=1024*1024,$=32*1024*1024,v=50,k=1e3,f=75e3,I="2024-11-05",A=256,b=new Set([...M,"event_tool_call"]);class D{server=null;port=0;authToken;invokeHandler=null;statusLineHandler=null;otelHandler=null;activeInvokes=0;wss=null;bridgeWs=null;mcpBridgeUpHandler=null;downstreamOutbox=[];mcpBridgeHttpActive=!1;pendingBridgeResponses=new Map;constructor(){this.authToken=E(32).toString("hex")}setInvokeHandler(e){this.invokeHandler=e}setStatusLineHandler(e){this.statusLineHandler=e}setOtelHandler(e){this.otelHandler=e}async start(e=0){return new Promise((t,r)=>{this.server=J.createServer((i,n)=>this.handleRequest(i,n)),this.wss=new R({noServer:!0}),this.server.on("upgrade",(i,n,s)=>{const o=new URL(i.url??"/",`http://127.0.0.1:${this.port}`);if(o.pathname!=="/mcp-bridge"||o.searchParams.get("token")!==this.authToken){n.destroy();return}this.wss.handleUpgrade(i,n,s,d=>{if(this.bridgeWs=d,c.info("internal-api","mcp bridge connected"),this.downstreamOutbox.length>0){const l=this.downstreamOutbox;this.downstreamOutbox=[];for(const u of l)d.send(u);c.info("internal-api",`mcp bridge flushed ${l.length} buffered downstream frame(s)`)}d.on("message",l=>{let u;try{u=JSON.parse(l.toString("utf8"))}catch{return}this.mcpBridgeUpHandler?.(u)}),d.on("close",()=>{this.bridgeWs===d&&(this.bridgeWs=null),c.info("internal-api","mcp bridge disconnected")}),d.on("error",()=>{})})}),this.server.on("error",r),this.server.listen(e,"127.0.0.1",()=>{const i=this.server.address();typeof i=="object"&&i&&(this.port=i.port),this.server.removeListener("error",r),t(this.port)})})}get url(){return`http://127.0.0.1:${this.port}?token=${this.authToken}`}get baseUrl(){return`http://127.0.0.1:${this.port}`}get mcpBridgeWsUrl(){return`ws://127.0.0.1:${this.port}/mcp-bridge?token=${this.authToken}`}get mcpBridgeHttpUrl(){return this.mcpBridgeHttpActive=!0,`${this.baseUrl}/mcp/app-bridge`}get mcpToolsHttpUrl(){return`${this.baseUrl}/mcp/tools`}setMcpBridgeUpHandler(e){this.mcpBridgeUpHandler=e}sendMcpFrameToBridge(e){const t=U(e);if(t!==void 0){const i=T(t),n=this.pendingBridgeResponses.get(i);if(n){this.pendingBridgeResponses.delete(i),clearTimeout(n.timer),n.resolve(e);return}}const r=JSON.stringify(e);if(this.bridgeWs&&this.bridgeWs.readyState===_.OPEN){this.bridgeWs.send(r);return}if(this.mcpBridgeHttpActive){c.debug("internal-api","dropping unsolicited MCP bridge frame without an HTTP stream");return}this.downstreamOutbox.length>=A&&(this.downstreamOutbox.shift(),c.warn("internal-api","mcp downstream outbox full, dropping oldest frame")),this.downstreamOutbox.push(r)}get token(){return this.authToken}async stop(){if(this.server){for(const[e,t]of this.pendingBridgeResponses)clearTimeout(t.timer),t.reject(new Error("internal API stopped")),this.pendingBridgeResponses.delete(e);return new Promise(e=>{this.server.close(()=>{this.server=null,e()})})}}checkAuth(e){return e.headers.authorization===`Bearer ${this.authToken}`||new URL(e.url??"/",`http://127.0.0.1:${this.port}`).searchParams.get("token")===this.authToken}handleRequest(e,t){if(e.method!=="POST"){t.writeHead(405),t.end(JSON.stringify({error:"method not allowed"}));return}if(!this.checkAuth(e)){t.writeHead(401),t.end(JSON.stringify({ok:!1,error:"unauthorized"}));return}const r=new URL(e.url??"/",`http://127.0.0.1:${this.port}`);if(r.pathname==="/mcp/tools"){this.handleMcpTools(e,t);return}if(r.pathname==="/mcp/app-bridge"){this.handleMcpBridgeHttp(e,t);return}if(r.pathname==="/api/status-line"){this.handleStatusLine(e,t);return}if(r.pathname==="/v1/logs"||r.pathname==="/v1/traces"){this.handleOtel(r.pathname==="/v1/logs"?"logs":"traces",e,t);return}if(!r.pathname.startsWith("/api/invoke")){t.writeHead(404),t.end(JSON.stringify({error:"not found"}));return}const i=[];let n=0;e.on("data",s=>{if(n+=s.length,n>w){t.writeHead(413),t.end(JSON.stringify({ok:!1,error:"request body too large"})),e.destroy();return}i.push(s)}),e.on("end",async()=>{let s;try{s=JSON.parse(Buffer.concat(i).toString("utf8"))}catch{t.writeHead(400),t.end(JSON.stringify({ok:!1,error:"invalid JSON"}));return}const o=s.action,d=s.params??{};if(!o||!this.invokeHandler){t.writeHead(400),t.end(JSON.stringify({ok:!1,error:"missing action or no handler"}));return}if(!b.has(o)){t.writeHead(400),t.end(JSON.stringify({ok:!1,error:`action '${o}' is not allowed`}));return}if(this.activeInvokes>=v){t.writeHead(429),t.end(JSON.stringify({ok:!1,error:"too many concurrent invokes"}));return}const l=s.timeout_ms!=null?Math.max(k,Math.min(s.timeout_ms,f)):f,u=Date.now();this.activeInvokes++;const m=new AbortController;try{const h=await Promise.race([this.invokeHandler(o,d,m.signal,l),new Promise((y,B)=>setTimeout(()=>{m.abort(),B(new Error("invoke timeout"))},l))]);t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify({ok:!0,data:h})),c.info("internal-api",`invoke ok action=${o} tool=${d.tool_name??"-"} ms=${Date.now()-u}`)}catch(h){m.abort();const y=h instanceof Error?h.message:String(h);t.writeHead(500),t.end(JSON.stringify({ok:!1,error:y})),c.error("internal-api",`invoke error action=${o} tool=${d.tool_name??"-"} ms=${Date.now()-u} err=${y}`)}finally{this.activeInvokes--}})}async handleMcpTools(e,t){let r;try{r=N(await H(e))}catch(o){p(t,null,-32700,o instanceof Error?o.message:String(o),400);return}if(r.method==="notifications/initialized"){t.writeHead(202),t.end();return}const i=r.id??null;if(r.method==="initialize"){g(t,i,{protocolVersion:I,capabilities:{tools:{}},serverInfo:{name:"grix-connector-tools",version:"1.0.0"}});return}if(r.method==="ping"){g(t,i,{});return}if(r.method==="tools/list"){g(t,i,{tools:S.map(({name:o,description:d,inputSchema:l})=>({name:o,description:d,inputSchema:l}))});return}if(r.method!=="tools/call"){p(t,i,-32601,`Method not found: ${r.method}`);return}const n=String(r.params?.name??""),s=r.params?.arguments??{};if(!S.some(o=>o.name===n)){p(t,i,-32602,`Unknown tool: ${n}`);return}try{const o=P(n,s),d=await this.invokeMcpTool(o.action,o.params,o.timeoutMs);g(t,i,{content:[{type:"text",text:JSON.stringify(d,null,2)??"null"}]})}catch(o){const d=o instanceof Error?o.message:String(o);g(t,i,{content:[{type:"text",text:JSON.stringify({error:d})}],isError:!0})}}async handleMcpBridgeHttp(e,t){let r;try{r=N(await H(e))}catch(n){p(t,null,-32700,n instanceof Error?n.message:String(n),400);return}if(!this.mcpBridgeUpHandler){p(t,r.id??null,-32603,"MCP bridge is unavailable",503);return}if(r.id===void 0){this.mcpBridgeUpHandler(r),t.writeHead(202),t.end();return}const i=T(r.id);if(this.pendingBridgeResponses.has(i)){p(t,r.id,-32600,"Duplicate in-flight MCP request id",409);return}try{const n=await new Promise((s,o)=>{const d=setTimeout(()=>{this.pendingBridgeResponses.delete(i),o(new Error("MCP bridge response timeout"))},f);this.pendingBridgeResponses.set(i,{resolve:s,reject:o,timer:d});try{this.mcpBridgeUpHandler(r)}catch(l){clearTimeout(d),this.pendingBridgeResponses.delete(i),o(l instanceof Error?l:new Error(String(l)))}});O(t,200,n)}catch(n){p(t,r.id,-32603,n instanceof Error?n.message:String(n),504)}}async invokeMcpTool(e,t,r){if(!this.invokeHandler)throw new Error("invoke handler is unavailable");if(!b.has(e))throw new Error(`action '${e}' is not allowed`);if(this.activeInvokes>=v)throw new Error("too many concurrent invokes");const i=r!=null?Math.max(k,Math.min(r,f)):f,n=new AbortController;let s;this.activeInvokes++;try{return await Promise.race([this.invokeHandler(e,t,n.signal,i),new Promise((o,d)=>{s=setTimeout(()=>{n.abort(),d(new Error("invoke timeout"))},i)})])}finally{s&&clearTimeout(s),this.activeInvokes--}}handleStatusLine(e,t){const r=[];let i=0;e.on("data",n=>{if(i+=n.length,i>w){t.writeHead(413),t.end(JSON.stringify({ok:!1,error:"request body too large"})),e.destroy();return}r.push(n)}),e.on("end",async()=>{let n;try{n=JSON.parse(Buffer.concat(r).toString("utf8"))}catch{t.writeHead(400),t.end(JSON.stringify({ok:!1,error:"invalid JSON"}));return}if(!this.statusLineHandler){t.writeHead(200),t.end(JSON.stringify({ok:!0}));return}try{await this.statusLineHandler(n),t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify({ok:!0}))}catch(s){console.error("[internal-api] statusLine handler error:",s instanceof Error?s.message:s),t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify({ok:!0}))}})}handleOtel(e,t,r){const i=[];let n=0,s=!1;t.on("data",o=>{if(!s){if(n+=o.length,n>$){s=!0,r.writeHead(413,{"content-type":"application/json"}),r.end(JSON.stringify({error:"OTLP request body too large"}));return}i.push(o)}}),t.on("end",async()=>{if(s)return;const o=Buffer.concat(i).toString("utf8");let d;try{const l=JSON.parse(o);if(!l||typeof l!="object"||Array.isArray(l))throw new Error("OTLP body is not an object");d=l}catch{r.writeHead(400,{"content-type":"application/json"}),r.end(JSON.stringify({error:"invalid OTLP JSON"}));return}try{await this.otelHandler?.(e,d,o)}catch(l){c.warn("internal-api",`OTLP ${e} evidence persistence failed: ${l instanceof Error?l.message:String(l)}`)}r.writeHead(200,{"content-type":"application/json"}),r.end("{}")})}}function H(a){return new Promise((e,t)=>{const r=[];let i=0,n=!1;a.on("data",s=>{if(!n){if(i+=s.length,i>w){n=!0,t(new Error("request body too large")),a.resume();return}r.push(s)}}),a.on("end",()=>{if(!n){n=!0;try{e(JSON.parse(Buffer.concat(r).toString("utf8")))}catch{t(new Error("invalid JSON"))}}}),a.on("error",s=>{n||(n=!0,t(s))})})}function N(a){if(!a||typeof a!="object"||Array.isArray(a))throw new Error("invalid JSON-RPC request");const e=a;if(e.jsonrpc!=="2.0"||typeof e.method!="string")throw new Error("invalid JSON-RPC request");const t=e.id;if(t!=null&&typeof t!="string"&&typeof t!="number")throw new Error("invalid JSON-RPC id");return a}function U(a){if(!a||typeof a!="object"||Array.isArray(a)||!Object.prototype.hasOwnProperty.call(a,"id"))return;const e=a.id;if(e===null||typeof e=="string"||typeof e=="number")return e}function T(a){return`${typeof a}:${String(a)}`}function O(a,e,t){a.writeHead(e,{"content-type":"application/json"}),a.end(JSON.stringify(t))}function g(a,e,t){O(a,200,{jsonrpc:"2.0",id:e,result:t})}function p(a,e,t,r,i=200){O(a,i,{jsonrpc:"2.0",id:e,error:{code:t,message:r}})}export{D as InternalApiServer};
|
|
1
|
+
import B from"node:http";import{randomBytes as M}from"node:crypto";import{log as h}from"../log/index.js";import{PHASE1_INVOKE_ACTIONS as C,TOOLS as O,toolCallToInvoke as P}from"./tools.js";import{WebSocketServer as R,WebSocket as $}from"ws";const y=1024*1024,U=32*1024*1024,N=50,k=1e3,f=75e3,H="2024-11-05",I=256,T=new Set([...C,"event_tool_call"]);class W{server=null;port=0;authToken;invokeHandler=null;statusLineHandler=null;otelHandler=null;activeInvokes=0;wss=null;bridgeWs=null;mcpBridgeUpHandler=null;downstreamOutbox=[];mcpBridgeHttpActive=!1;pendingBridgeResponses=new Map;codebaseNavigatorCwd=null;codebaseNavigatorFacade=null;constructor(){this.authToken=M(32).toString("hex")}setInvokeHandler(e){this.invokeHandler=e}setStatusLineHandler(e){this.statusLineHandler=e}setOtelHandler(e){this.otelHandler=e}async start(e=0){return new Promise((t,r)=>{this.server=B.createServer((n,i)=>this.handleRequest(n,i)),this.wss=new R({noServer:!0}),this.server.on("upgrade",(n,i,s)=>{const o=new URL(n.url??"/",`http://127.0.0.1:${this.port}`);if(o.pathname!=="/mcp-bridge"||o.searchParams.get("token")!==this.authToken){i.destroy();return}this.wss.handleUpgrade(n,i,s,a=>{if(this.bridgeWs=a,h.info("internal-api","mcp bridge connected"),this.downstreamOutbox.length>0){const c=this.downstreamOutbox;this.downstreamOutbox=[];for(const p of c)a.send(p);h.info("internal-api",`mcp bridge flushed ${c.length} buffered downstream frame(s)`)}a.on("message",c=>{let p;try{p=JSON.parse(c.toString("utf8"))}catch{return}this.mcpBridgeUpHandler?.(p)}),a.on("close",()=>{this.bridgeWs===a&&(this.bridgeWs=null),h.info("internal-api","mcp bridge disconnected")}),a.on("error",()=>{})})}),this.server.on("error",r),this.server.listen(e,"127.0.0.1",()=>{const n=this.server.address();typeof n=="object"&&n&&(this.port=n.port),this.server.removeListener("error",r),t(this.port)})})}get url(){return`http://127.0.0.1:${this.port}?token=${this.authToken}`}get baseUrl(){return`http://127.0.0.1:${this.port}`}get mcpBridgeWsUrl(){return`ws://127.0.0.1:${this.port}/mcp-bridge?token=${this.authToken}`}get mcpBridgeHttpUrl(){return this.mcpBridgeHttpActive=!0,`${this.baseUrl}/mcp/app-bridge`}get mcpToolsHttpUrl(){return`${this.baseUrl}/mcp/tools`}get codebaseNavigatorHttpUrl(){return`${this.baseUrl}/mcp/codebase-navigator`}async setCodebaseNavigatorCwd(e){if(this.codebaseNavigatorCwd===e&&this.codebaseNavigatorFacade)return;this.codebaseNavigatorCwd=e;const t=this.codebaseNavigatorFacade;this.codebaseNavigatorFacade=null,await t?.close();const i=(await import(new URL("../../kimi-plugins/grix-layer/scripts/http-facade.mjs",import.meta.url).href)).createCodebaseNavigatorHttpFacade;this.codebaseNavigatorFacade=i({cwd:e})}setMcpBridgeUpHandler(e){this.mcpBridgeUpHandler=e}sendMcpFrameToBridge(e){const t=_(e);if(t!==void 0){const n=E(t),i=this.pendingBridgeResponses.get(n);if(i){this.pendingBridgeResponses.delete(n),clearTimeout(i.timer),i.resolve(e);return}}const r=JSON.stringify(e);if(this.bridgeWs&&this.bridgeWs.readyState===$.OPEN){this.bridgeWs.send(r);return}if(this.mcpBridgeHttpActive){h.debug("internal-api","dropping unsolicited MCP bridge frame without an HTTP stream");return}this.downstreamOutbox.length>=I&&(this.downstreamOutbox.shift(),h.warn("internal-api","mcp downstream outbox full, dropping oldest frame")),this.downstreamOutbox.push(r)}get token(){return this.authToken}async stop(){const e=this.codebaseNavigatorFacade;if(this.codebaseNavigatorFacade=null,await e?.close(),!!this.server){for(const[t,r]of this.pendingBridgeResponses)clearTimeout(r.timer),r.reject(new Error("internal API stopped")),this.pendingBridgeResponses.delete(t);return new Promise(t=>{this.server.close(()=>{this.server=null,t()})})}}checkAuth(e){return e.headers.authorization===`Bearer ${this.authToken}`||new URL(e.url??"/",`http://127.0.0.1:${this.port}`).searchParams.get("token")===this.authToken}handleRequest(e,t){if(e.method!=="POST"){t.writeHead(405),t.end(JSON.stringify({error:"method not allowed"}));return}if(!this.checkAuth(e)){t.writeHead(401),t.end(JSON.stringify({ok:!1,error:"unauthorized"}));return}const r=new URL(e.url??"/",`http://127.0.0.1:${this.port}`);if(r.pathname==="/mcp/tools"){this.handleMcpTools(e,t);return}if(r.pathname==="/mcp/app-bridge"){this.handleMcpBridgeHttp(e,t);return}if(r.pathname==="/mcp/codebase-navigator"){this.handleCodebaseNavigatorMcp(e,t);return}if(r.pathname==="/api/status-line"){this.handleStatusLine(e,t);return}if(r.pathname==="/v1/logs"||r.pathname==="/v1/traces"){this.handleOtel(r.pathname==="/v1/logs"?"logs":"traces",e,t);return}if(!r.pathname.startsWith("/api/invoke")){t.writeHead(404),t.end(JSON.stringify({error:"not found"}));return}const n=[];let i=0;e.on("data",s=>{if(i+=s.length,i>y){t.writeHead(413),t.end(JSON.stringify({ok:!1,error:"request body too large"})),e.destroy();return}n.push(s)}),e.on("end",async()=>{let s;try{s=JSON.parse(Buffer.concat(n).toString("utf8"))}catch{t.writeHead(400),t.end(JSON.stringify({ok:!1,error:"invalid JSON"}));return}const o=s.action,a=s.params??{};if(!o||!this.invokeHandler){t.writeHead(400),t.end(JSON.stringify({ok:!1,error:"missing action or no handler"}));return}if(!T.has(o)){t.writeHead(400),t.end(JSON.stringify({ok:!1,error:`action '${o}' is not allowed`}));return}if(this.activeInvokes>=N){t.writeHead(429),t.end(JSON.stringify({ok:!1,error:"too many concurrent invokes"}));return}const c=s.timeout_ms!=null?Math.max(k,Math.min(s.timeout_ms,f)):f,p=Date.now();this.activeInvokes++;const m=new AbortController;try{const g=await Promise.race([this.invokeHandler(o,a,m.signal,c),new Promise((w,J)=>setTimeout(()=>{m.abort(),J(new Error("invoke timeout"))},c))]);t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify({ok:!0,data:g})),h.info("internal-api",`invoke ok action=${o} tool=${a.tool_name??"-"} ms=${Date.now()-p}`)}catch(g){m.abort();const w=g instanceof Error?g.message:String(g);t.writeHead(500),t.end(JSON.stringify({ok:!1,error:w})),h.error("internal-api",`invoke error action=${o} tool=${a.tool_name??"-"} ms=${Date.now()-p} err=${w}`)}finally{this.activeInvokes--}})}async handleMcpTools(e,t){let r;try{r=b(await v(e))}catch(o){l(t,null,-32700,o instanceof Error?o.message:String(o),400);return}if(r.method==="notifications/initialized"){t.writeHead(202),t.end();return}const n=r.id??null;if(r.method==="initialize"){u(t,n,{protocolVersion:H,capabilities:{tools:{}},serverInfo:{name:"grix-connector-tools",version:"1.0.0"}});return}if(r.method==="ping"){u(t,n,{});return}if(r.method==="tools/list"){u(t,n,{tools:O.map(({name:o,description:a,inputSchema:c})=>({name:o,description:a,inputSchema:c}))});return}if(r.method!=="tools/call"){l(t,n,-32601,`Method not found: ${r.method}`);return}const i=String(r.params?.name??""),s=r.params?.arguments??{};if(!O.some(o=>o.name===i)){l(t,n,-32602,`Unknown tool: ${i}`);return}try{const o=P(i,s),a=await this.invokeMcpTool(o.action,o.params,o.timeoutMs);u(t,n,{content:[{type:"text",text:JSON.stringify(a,null,2)??"null"}]})}catch(o){const a=o instanceof Error?o.message:String(o);u(t,n,{content:[{type:"text",text:JSON.stringify({error:a})}],isError:!0})}}async handleMcpBridgeHttp(e,t){let r;try{r=b(await v(e))}catch(i){l(t,null,-32700,i instanceof Error?i.message:String(i),400);return}if(!this.mcpBridgeUpHandler){l(t,r.id??null,-32603,"MCP bridge is unavailable",503);return}if(r.id===void 0){this.mcpBridgeUpHandler(r),t.writeHead(202),t.end();return}const n=E(r.id);if(this.pendingBridgeResponses.has(n)){l(t,r.id,-32600,"Duplicate in-flight MCP request id",409);return}try{const i=await new Promise((s,o)=>{const a=setTimeout(()=>{this.pendingBridgeResponses.delete(n),o(new Error("MCP bridge response timeout"))},f);this.pendingBridgeResponses.set(n,{resolve:s,reject:o,timer:a});try{this.mcpBridgeUpHandler(r)}catch(c){clearTimeout(a),this.pendingBridgeResponses.delete(n),o(c instanceof Error?c:new Error(String(c)))}});S(t,200,i)}catch(i){l(t,r.id,-32603,i instanceof Error?i.message:String(i),504)}}async handleCodebaseNavigatorMcp(e,t){let r;try{r=b(await v(e))}catch(a){l(t,null,-32700,a instanceof Error?a.message:String(a),400);return}if(r.method==="notifications/initialized"){t.writeHead(202),t.end();return}const n=r.id??null;if(r.method==="initialize"){u(t,n,{protocolVersion:H,capabilities:{tools:{}},serverInfo:{name:"codebase-navigator",version:"0.1.0"}});return}if(r.method==="ping"){u(t,n,{});return}let i;try{i=await this.getCodebaseNavigatorFacade()}catch(a){l(t,n,-32603,a instanceof Error?a.message:String(a),503);return}if(r.method==="tools/list"){u(t,n,{tools:i.listTools()});return}if(r.method!=="tools/call"){l(t,n,-32601,`Method not found: ${r.method}`);return}const s=String(r.params?.name??""),o=r.params?.arguments??{};if(!i.listTools().some(a=>a.name===s)){l(t,n,-32602,`Unknown tool: ${s}`);return}try{u(t,n,await i.callTool(s,o))}catch(a){u(t,n,{content:[{type:"text",text:JSON.stringify({error:a instanceof Error?a.message:String(a)})}],isError:!0})}}async getCodebaseNavigatorFacade(){if(this.codebaseNavigatorFacade)return this.codebaseNavigatorFacade;if(!this.codebaseNavigatorCwd)throw new Error("codebase navigator cwd is not configured");if(await this.setCodebaseNavigatorCwd(this.codebaseNavigatorCwd),!this.codebaseNavigatorFacade)throw new Error("codebase navigator failed to initialize");return this.codebaseNavigatorFacade}async invokeMcpTool(e,t,r){if(!this.invokeHandler)throw new Error("invoke handler is unavailable");if(!T.has(e))throw new Error(`action '${e}' is not allowed`);if(this.activeInvokes>=N)throw new Error("too many concurrent invokes");const n=r!=null?Math.max(k,Math.min(r,f)):f,i=new AbortController;let s;this.activeInvokes++;try{return await Promise.race([this.invokeHandler(e,t,i.signal,n),new Promise((o,a)=>{s=setTimeout(()=>{i.abort(),a(new Error("invoke timeout"))},n)})])}finally{s&&clearTimeout(s),this.activeInvokes--}}handleStatusLine(e,t){const r=[];let n=0;e.on("data",i=>{if(n+=i.length,n>y){t.writeHead(413),t.end(JSON.stringify({ok:!1,error:"request body too large"})),e.destroy();return}r.push(i)}),e.on("end",async()=>{let i;try{i=JSON.parse(Buffer.concat(r).toString("utf8"))}catch{t.writeHead(400),t.end(JSON.stringify({ok:!1,error:"invalid JSON"}));return}if(!this.statusLineHandler){t.writeHead(200),t.end(JSON.stringify({ok:!0}));return}try{await this.statusLineHandler(i),t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify({ok:!0}))}catch(s){console.error("[internal-api] statusLine handler error:",s instanceof Error?s.message:s),t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify({ok:!0}))}})}handleOtel(e,t,r){const n=[];let i=0,s=!1;t.on("data",o=>{if(!s){if(i+=o.length,i>U){s=!0,r.writeHead(413,{"content-type":"application/json"}),r.end(JSON.stringify({error:"OTLP request body too large"}));return}n.push(o)}}),t.on("end",async()=>{if(s)return;const o=Buffer.concat(n).toString("utf8");let a;try{const c=JSON.parse(o);if(!c||typeof c!="object"||Array.isArray(c))throw new Error("OTLP body is not an object");a=c}catch{r.writeHead(400,{"content-type":"application/json"}),r.end(JSON.stringify({error:"invalid OTLP JSON"}));return}try{await this.otelHandler?.(e,a,o)}catch(c){h.warn("internal-api",`OTLP ${e} evidence persistence failed: ${c instanceof Error?c.message:String(c)}`)}r.writeHead(200,{"content-type":"application/json"}),r.end("{}")})}}function v(d){return new Promise((e,t)=>{const r=[];let n=0,i=!1;d.on("data",s=>{if(!i){if(n+=s.length,n>y){i=!0,t(new Error("request body too large")),d.resume();return}r.push(s)}}),d.on("end",()=>{if(!i){i=!0;try{e(JSON.parse(Buffer.concat(r).toString("utf8")))}catch{t(new Error("invalid JSON"))}}}),d.on("error",s=>{i||(i=!0,t(s))})})}function b(d){if(!d||typeof d!="object"||Array.isArray(d))throw new Error("invalid JSON-RPC request");const e=d;if(e.jsonrpc!=="2.0"||typeof e.method!="string")throw new Error("invalid JSON-RPC request");const t=e.id;if(t!=null&&typeof t!="string"&&typeof t!="number")throw new Error("invalid JSON-RPC id");return d}function _(d){if(!d||typeof d!="object"||Array.isArray(d)||!Object.prototype.hasOwnProperty.call(d,"id"))return;const e=d.id;if(e===null||typeof e=="string"||typeof e=="number")return e}function E(d){return`${typeof d}:${String(d)}`}function S(d,e,t){d.writeHead(e,{"content-type":"application/json"}),d.end(JSON.stringify(t))}function u(d,e,t){S(d,200,{jsonrpc:"2.0",id:e,result:t})}function l(d,e,t,r,n=200){S(d,n,{jsonrpc:"2.0",id:e,error:{code:t,message:r}})}export{W as InternalApiServer};
|
package/dist/grix.js
CHANGED
|
File without changes
|
|
@@ -92,13 +92,17 @@ Keep configured paths repository-relative. The Gateway reads at most 64 KiB and
|
|
|
92
92
|
|
|
93
93
|
## Installation
|
|
94
94
|
|
|
95
|
+
Grix-managed Kimi sessions receive this Gateway through the connector's authenticated loopback HTTP MCP endpoint. Do not add a plugin-level stdio MCP entry for those sessions: Kimi 0.38 routes stdio children through its Bash-only ACP runtime.
|
|
96
|
+
|
|
97
|
+
For standalone Agents that execute MCP children locally, configure the stdio server directly:
|
|
98
|
+
|
|
95
99
|
Install dependencies in the skill directory:
|
|
96
100
|
|
|
97
101
|
```bash
|
|
98
102
|
npm install
|
|
99
103
|
```
|
|
100
104
|
|
|
101
|
-
Configure only this MCP server
|
|
105
|
+
Configure only this MCP server:
|
|
102
106
|
|
|
103
107
|
```json
|
|
104
108
|
{
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { DownstreamClient } from "./lib/downstream-client.mjs";
|
|
2
|
+
import { NavigationService } from "./lib/navigation-service.mjs";
|
|
3
|
+
import {
|
|
4
|
+
callCodebaseNavigatorTool,
|
|
5
|
+
listCodebaseNavigatorTools,
|
|
6
|
+
} from "./lib/tool-server.mjs";
|
|
7
|
+
|
|
8
|
+
export function createCodebaseNavigatorHttpFacade({ cwd }) {
|
|
9
|
+
const downstream = new DownstreamClient({ cwd });
|
|
10
|
+
const service = new NavigationService({ downstream, cwd });
|
|
11
|
+
return {
|
|
12
|
+
listTools: () => listCodebaseNavigatorTools(),
|
|
13
|
+
callTool: (name, args) => callCodebaseNavigatorTool(service, name, args),
|
|
14
|
+
close: () => downstream.close(),
|
|
15
|
+
};
|
|
16
|
+
}
|