grix-connector 4.0.0 → 4.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/adapter/acp/acp-adapter.js +9 -9
  2. package/dist/assets/dsh-bridge/{grix-dsh-bridge-4.0.0.tgz → grix-dsh-bridge-4.0.1.tgz} +0 -0
  3. package/dist/assets/dsh-bridge/manifest.json +5 -5
  4. package/dist/core/mcp/internal-api-server.js +1 -1
  5. package/dist/grix.js +0 -0
  6. package/dist/kimi-plugins/grix-layer/references/gateway-architecture.md +5 -1
  7. package/dist/kimi-plugins/grix-layer/scripts/http-facade.mjs +16 -0
  8. package/dist/kimi-plugins/grix-layer/scripts/lib/tool-server.mjs +74 -59
  9. package/dist/kimi-plugins/index.js +2 -3
  10. package/dist/protocol/acp-client.js +1 -1
  11. package/dist/protocol/acp-terminal.js +2 -0
  12. package/package.json +1 -1
  13. package/dist/adapter/claude/claude-bridge-server.js +0 -1
  14. package/dist/adapter/claude/claude-tools.js +0 -1
  15. package/dist/adapter/claude/claude-worker-client.js +0 -1
  16. package/dist/adapter/claude/mcp-http-launcher.js +0 -2
  17. package/dist/adapter/claude/result-timeout.js +0 -1
  18. package/dist/adapter/deepseek/deepseek-adapter.js +0 -6
  19. package/dist/adapter/deepseek/index.js +0 -1
  20. package/dist/adapter/qwen/index.js +0 -1
  21. package/dist/adapter/qwen/qwen-adapter.js +0 -4
  22. package/dist/aibot/client.js +0 -1
  23. package/dist/aibot/index.js +0 -1
  24. package/dist/aibot/types.js +0 -0
  25. package/dist/core/access/allowlist-gate.js +0 -1
  26. package/dist/core/access/allowlist-store.js +0 -1
  27. package/dist/core/access/index.js +0 -1
  28. package/dist/core/file-ops/handler.js +0 -1
  29. package/dist/core/file-ops/list-files.js +0 -1
  30. package/dist/core/file-ops/types.js +0 -0
  31. package/dist/log.js +0 -3
  32. package/dist/main.js +0 -31
  33. package/dist/mcp/stream-http/config.js +0 -1
  34. package/dist/mcp/stream-http/connection-binding.js +0 -1
  35. package/dist/mcp/stream-http/event-tool-executor.js +0 -1
  36. package/dist/mcp/stream-http/gateway.js +0 -1
  37. package/dist/mcp/stream-http/index.js +0 -1
  38. package/dist/mcp/stream-http/security.js +0 -1
  39. package/dist/mcp/stream-http/session-manager.js +0 -1
  40. package/dist/mcp/stream-http/tool-executor.js +0 -1
  41. package/dist/mcp/stream-http/tool-registry.js +0 -1
  42. package/dist/mcp/stream-http/tool-schemas.js +0 -1
  43. package/dist/session/index.js +0 -1
  44. package/dist/session/manager.js +0 -1
  45. package/dist/transport/index.js +0 -1
  46. package/dist/transport/json-rpc.js +0 -3
@@ -1,21 +1,21 @@
1
- import{fileURLToPath as H}from"node:url";import h 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(b(t))}catch{return e}}function b(c){if(c===null||typeof c!="object")return c;if(Array.isArray(c))return c.map(b);const e=c,t=Object.keys(e).sort(),i={};for(const s of t)i[s]=b(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=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)}`),be(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+=`
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(h.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()),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,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 p=o?"binding_recreated":"binding_ready",u=this.buildToolbarContext(p,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=h.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=h.join(v(),".grix","data","session-summaries");await g.mkdir(o,{recursive:!0});const a=h.join(o,`${e}.md`),l=[`SOURCE_KIRO_SESSION_FILE: ${s}`,"",n.trim(),""].join(`
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:this.isKimi,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 p=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(p),o(u)}),a.on("close",u=>{if(clearTimeout(p),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?.removeAllListeners(),this.acpClient=null,this.emit("exit",null)}async startInternalApiAndMcp(){try{this.internalApi||(this.internalApi=new ne,this.internalApi.setInvokeHandler(async(o,a,l,d)=>this.callbacks.agentInvoke(o,a,d)),await this.internalApi.start(0),r.info("acp-adapter",`Internal API started at ${this.internalApi.url}`)),this.internalApi.setMcpBridgeUpHandler(o=>this.onMcpBridgeUp(o));const e=this.config.command??"",t=e.includes("qwen")?h.join(v(),".qwen","skills"):e.includes("gemini")?h.join(v(),".gemini","skills"):e.includes("kiro")?h.join(v(),".kiro","skills"):this.isReasonix?h.join(v(),".reasonix","skills"):this.isKimi?h.join(J({...process.env,...this.config.env}),"skills"):this.auditAgentType==="copilot"||/^copilot(?:\.cmd)?$/i.test(h.basename(e).toLowerCase())||/^gh(?:\.exe)?$/i.test(h.basename(e).toLowerCase())&&(this.config.args??[]).includes("copilot")?h.join(v(),".copilot","skills"):this.auditAgentType==="hermes"?h.join(this.config.env?.HERMES_HOME?.trim()||Z({...process.env,...this.config.env},this.nativeProviderScope),"skills"):void 0;if(t){const o=X(t);o.length>0&&r.info("acp-adapter",`Synced connector skills to ${t}: [${o.join(", ")}]`)}const i=h.resolve(w,"../../mcp/mcp-bridge-server.js"),s=this.internalApi.mcpBridgeWsUrl,n=h.resolve(w,"../../mcp/acp-mcp-server.js");if(this.isKimi){const o={Authorization:`Bearer ${this.internalApi.token}`};return[{type:"http",name:"grix-app-bridge",url:this.internalApi.mcpBridgeHttpUrl,headers:o},{type:"http",name:"grix-connector-tools",url:this.internalApi.mcpToolsHttpUrl,headers:o}]}return[{name:"grix-app-bridge",command:process.execPath,args:[i,"--ws-url",s],...!this.isReasonix&&{env:{GRIX_CONNECTOR_MCP_BRIDGE_WS:s}}},{name:"grix-connector-tools",command:process.execPath,args:[n,"--api-url",this.internalApi.url],...!this.isReasonix&&{env:{}}}]}catch(e){r.error("acp-adapter",`Failed to start MCP tools: ${e}`);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.clearSettleTimer(),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:
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:this.isKimi,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<j||this.pendingAutoCompact||this.compacting||(this.pendingAutoCompact=!0,r.info("acp-adapter",`[auto-compact] context_window usedPercentage=${t.toFixed(1)}% >= ${j}%, 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};
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,9 +1,9 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "bridgeVersion": "4.0.0",
4
- "tarball": "grix-dsh-bridge-4.0.0.tgz",
5
- "size": 22006,
3
+ "bridgeVersion": "4.0.1",
4
+ "tarball": "grix-dsh-bridge-4.0.1.tgz",
5
+ "size": 22008,
6
6
  "unpackedSize": 87631,
7
- "shasum": "0339a6cd18a101c3c0ee152d72f0f6d08d2f392a",
8
- "integrity": "sha512-06N1ubbuEOTP0PdxsAENmu0IRVP3YBoCDJUaSNHMOi1FEgvRrF3Q/Kq8j/WH/gJTPfEvQMuDiSNZNxIV/lbzeg=="
7
+ "shasum": "2b87cc453a861de9e491e5fe37d96bbf782b544a",
8
+ "integrity": "sha512-48Kx3cTARnuY5+o8/f1me/f122v91dmhuOyurHi9hORe/jGncPtBvQy3ZDpTjBpccLch1YcI9WXxG+PIi1jErA=="
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 for ordinary Agents:
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
+ }
@@ -19,6 +19,49 @@ const nextActions = Object.freeze({
19
19
  stale_index: "offer_index_refresh",
20
20
  });
21
21
 
22
+ const toolSpecs = [
23
+ {
24
+ name: "locate_code",
25
+ title: "Locate code by architecture layer",
26
+ description:
27
+ "Default code-discovery entry point. Classifies the task into an architecture layer, searches only bounded module scopes, verifies one-hop relations, and returns line ranges without source code.",
28
+ inputSchema: z.object({
29
+ problem: z.string().min(3).max(2000).describe("Complete bug, feature, error, or code-navigation question."),
30
+ project: z.string().max(120).optional().describe("Indexed project name; omit when cwd identifies it."),
31
+ knownSymbol: z.string().max(240).optional().describe("Exact or partial symbol already known."),
32
+ fileHint: z.string().max(500).optional().describe("Known repository-relative path or path fragment."),
33
+ layer: z.string().max(60).optional().describe("Layer id only when already established by a prior outline."),
34
+ }),
35
+ run: (service, args) => service.locateCode(args),
36
+ },
37
+ {
38
+ name: "refine_location",
39
+ title: "Refine an ambiguous location",
40
+ description:
41
+ "Continue a locate_code session by selecting one layer or candidate, or by adding a short discriminating hint. Maximum two refinements.",
42
+ inputSchema: z.object({
43
+ locatorId: z.string().uuid(),
44
+ layer: z.string().max(60).optional(),
45
+ candidateId: z.string().max(20).optional(),
46
+ additionalHint: z.string().max(400).optional(),
47
+ }).refine(
48
+ (value) => Boolean(value.layer || value.candidateId || value.additionalHint),
49
+ "Provide layer, candidateId, or additionalHint.",
50
+ ),
51
+ run: (service, args) => service.refineLocation(args),
52
+ },
53
+ {
54
+ name: "get_project_outline",
55
+ title: "Get compact project layers",
56
+ description:
57
+ "Return only the compact architecture layers and module paths. Use when locate_code reports architecture_uncertain, not as a routine first call.",
58
+ inputSchema: z.object({
59
+ project: z.string().max(120).optional(),
60
+ }),
61
+ run: (service, args) => service.getProjectOutline(args),
62
+ },
63
+ ];
64
+
22
65
  export function errorResult(error) {
23
66
  const known = error instanceof NavigatorError;
24
67
  return toolResult(
@@ -34,14 +77,25 @@ export function errorResult(error) {
34
77
  );
35
78
  }
36
79
 
37
- function handler(callback) {
38
- return async (args) => {
39
- try {
40
- return toolResult(await callback(args));
41
- } catch (error) {
42
- return errorResult(error);
43
- }
44
- };
80
+ export function listCodebaseNavigatorTools() {
81
+ return toolSpecs.map(({ name, title, description, inputSchema }) => ({
82
+ name,
83
+ title,
84
+ description,
85
+ inputSchema: z.toJSONSchema(inputSchema),
86
+ annotations: readOnly,
87
+ }));
88
+ }
89
+
90
+ export async function callCodebaseNavigatorTool(service, name, args) {
91
+ const spec = toolSpecs.find((tool) => tool.name === name);
92
+ if (!spec) throw new NavigatorError("invalid_tool", `未知定位工具:${name}`);
93
+ try {
94
+ const parsed = spec.inputSchema.parse(args);
95
+ return toolResult(await spec.run(service, parsed));
96
+ } catch (error) {
97
+ return errorResult(error);
98
+ }
45
99
  }
46
100
 
47
101
  export function createToolServer(service) {
@@ -50,57 +104,18 @@ export function createToolServer(service) {
50
104
  { capabilities: { tools: {} } },
51
105
  );
52
106
 
53
- server.registerTool(
54
- "locate_code",
55
- {
56
- title: "Locate code by architecture layer",
57
- description:
58
- "Default code-discovery entry point. Classifies the task into an architecture layer, searches only bounded module scopes, verifies one-hop relations, and returns line ranges without source code.",
59
- inputSchema: z.object({
60
- problem: z.string().min(3).max(2000).describe("Complete bug, feature, error, or code-navigation question."),
61
- project: z.string().max(120).optional().describe("Indexed project name; omit when cwd identifies it."),
62
- knownSymbol: z.string().max(240).optional().describe("Exact or partial symbol already known."),
63
- fileHint: z.string().max(500).optional().describe("Known repository-relative path or path fragment."),
64
- layer: z.string().max(60).optional().describe("Layer id only when already established by a prior outline."),
65
- }),
66
- annotations: readOnly,
67
- },
68
- handler((args) => service.locateCode(args)),
69
- );
70
-
71
- server.registerTool(
72
- "refine_location",
73
- {
74
- title: "Refine an ambiguous location",
75
- description:
76
- "Continue a locate_code session by selecting one layer or candidate, or by adding a short discriminating hint. Maximum two refinements.",
77
- inputSchema: z.object({
78
- locatorId: z.string().uuid(),
79
- layer: z.string().max(60).optional(),
80
- candidateId: z.string().max(20).optional(),
81
- additionalHint: z.string().max(400).optional(),
82
- }).refine(
83
- (value) => Boolean(value.layer || value.candidateId || value.additionalHint),
84
- "Provide layer, candidateId, or additionalHint.",
85
- ),
86
- annotations: readOnly,
87
- },
88
- handler((args) => service.refineLocation(args)),
89
- );
90
-
91
- server.registerTool(
92
- "get_project_outline",
93
- {
94
- title: "Get compact project layers",
95
- description:
96
- "Return only the compact architecture layers and module paths. Use when locate_code reports architecture_uncertain, not as a routine first call.",
97
- inputSchema: z.object({
98
- project: z.string().max(120).optional(),
99
- }),
100
- annotations: readOnly,
101
- },
102
- handler((args) => service.getProjectOutline(args)),
103
- );
107
+ for (const spec of toolSpecs) {
108
+ server.registerTool(
109
+ spec.name,
110
+ {
111
+ title: spec.title,
112
+ description: spec.description,
113
+ inputSchema: spec.inputSchema,
114
+ annotations: readOnly,
115
+ },
116
+ (args) => callCodebaseNavigatorTool(service, spec.name, args),
117
+ );
118
+ }
104
119
 
105
120
  return server;
106
121
  }
@@ -1,3 +1,2 @@
1
- import{cpSync as I,existsSync as E,mkdirSync as x,readFileSync as R,renameSync as M,writeFileSync as p}from"node:fs";import{basename as O,dirname as g,join as i}from"node:path";import{fileURLToPath as _}from"node:url";import{resolveKimiCodeHome as w}from"../default-skills/index.js";const N=g(_(import.meta.url)),u="grix-layer",B=".grix-managed";function $(r){if(!E(r))return{version:1,plugins:[]};const e=JSON.parse(R(r,"utf8"));if(e.version!==1||!Array.isArray(e.plugins))throw new Error(`Invalid Kimi plugin registry: ${r}`);return e}function P(r=process.env){const e=w(r),l=i(N,u),n=i(e,"plugins",u),m=i(l,"kimi.plugin.json");if(!E(m))throw new Error(`Bundled Kimi Layer plugin is missing: ${m}`);x(g(n),{recursive:!0}),I(l,n,{recursive:!0,force:!0}),p(i(n,B),"","utf8");const f=i(n,"kimi.plugin.json"),h=JSON.parse(R(f,"utf8")),y=r.CODEBASE_MEMORY_BINARY?.trim();p(f,`${JSON.stringify({...h,mcpServers:{"codebase-navigator":{command:O(process.execPath),args:[i(l,"scripts","gateway.mjs")],env:y?{CODEBASE_MEMORY_BINARY:y}:{}}}},null,2)}
2
- `,"utf8");const s=i(e,"plugins","installed.json"),o=$(s),c=o.plugins.findIndex(a=>a.id===u),t=c>=0?o.plugins[c]:void 0,S=t?.root===n&&t.source==="local-path"&&t.enabled===!0;if(!S){const a=new Date().toISOString(),A={...t,id:u,root:n,source:"local-path",enabled:!0,installedAt:t?.installedAt??a,updatedAt:t?a:void 0,originalSource:n},d=[...o.plugins];c>=0?d[c]=A:d.push(A),x(g(s),{recursive:!0});const v=`${s}.grix-${process.pid}.tmp`;p(v,`${JSON.stringify({...o,version:1,plugins:d},null,2)}
3
- `,{encoding:"utf8",mode:384}),M(v,s)}return{kimiHome:e,pluginRoot:n,installedFile:s,registrationChanged:!S}}export{P as prepareKimiLayerPlugin};
1
+ import{cpSync as A,existsSync as S,mkdirSync as v,readFileSync as h,renameSync as w,writeFileSync as x}from"node:fs";import{dirname as d,join as o}from"node:path";import{fileURLToPath as I}from"node:url";import{resolveKimiCodeHome as R}from"../default-skills/index.js";const E=d(I(import.meta.url)),l="grix-layer",K=".grix-managed";function L(r){if(!S(r))return{version:1,plugins:[]};const n=JSON.parse(h(r,"utf8"));if(n.version!==1||!Array.isArray(n.plugins))throw new Error(`Invalid Kimi plugin registry: ${r}`);return n}function P(r=process.env){const n=R(r),p=o(E,l),e=o(n,"plugins",l),g=o(p,"kimi.plugin.json");if(!S(g))throw new Error(`Bundled Kimi Layer plugin is missing: ${g}`);v(d(e),{recursive:!0}),A(p,e,{recursive:!0,force:!0}),x(o(e,K),"","utf8");const t=o(n,"plugins","installed.json"),s=L(t),u=s.plugins.findIndex(c=>c.id===l),i=u>=0?s.plugins[u]:void 0,m=i?.root===e&&i.source==="local-path"&&i.enabled===!0;if(!m){const c=new Date().toISOString(),f={...i,id:l,root:e,source:"local-path",enabled:!0,installedAt:i?.installedAt??c,updatedAt:i?c:void 0,originalSource:e},a=[...s.plugins];u>=0?a[u]=f:a.push(f),v(d(t),{recursive:!0});const y=`${t}.grix-${process.pid}.tmp`;x(y,`${JSON.stringify({...s,version:1,plugins:a},null,2)}
2
+ `,{encoding:"utf8",mode:384}),w(y,t)}return{kimiHome:n,pluginRoot:e,installedFile:t,registrationChanged:!m}}export{P as prepareKimiLayerPlugin};
@@ -1 +1 @@
1
- import{EventEmitter as S}from"events";import{log as r}from"../core/log/index.js";import{AgentEventType as c}from"../types/events.js";import{mapSessionUpdate as b}from"./event-mapper.js";class p extends Error{authMethods;constructor(e){super("ACP authentication required"),this.name="AcpAuthRequiredError",this.authMethods=e}}function h(l){if(!l||typeof l!="object")return!1;const e=l;return e.code===-32e3?!0:e.code===-32603&&e.data?.details?/\b(401|token\s*expired|access\s*token)\b/i.test(e.data.details):!1}function m(l){return!l||typeof l!="object"?[{id:"oauth"}]:l.data?.authMethods??[{id:"oauth"}]}class u extends S{acpSessionId="";alive=!1;pendingPermissions=new Map;availableModes=[];availableModels=[];currentMode="";currentModel="";listSupported=!1;loadSupported=!1;_supportsCommandsExecute=!1;_availableCommands=[];transport;settleTimer=null;static SETTLE_MS=3e3;pendingToolCallIds=new Set;settleSuppressedSince=0;static SETTLE_SUPPRESS_MAX_MS=900*1e3;constructor(){super(),this.transport=null}get sessionId(){return this.acpSessionId}get isAlive(){return this.alive}get modes(){return[...this.availableModes]}get mode(){return this.currentMode}get models(){return[...this.availableModels]}get model(){return this.currentModel}get sessionOptions(){return{modes:[...this.availableModes],currentModeId:this.currentMode,models:[...this.availableModels],currentModelId:this.currentModel}}get supportsCommandsExecute(){return this._supportsCommandsExecute}get availableCommands(){return[...this._availableCommands]}async connect(e){this.transport=e.transport,this.transport.on("close",()=>{this.alive&&(this.alive=!1,this.emit("session-lost"))}),this.transport.setHandlers((n,a)=>{n==="session/update"?this.handleSessionUpdate(a):n==="_kiro.dev/metadata"?this.handleKiroMetadata(a):n==="_kiro.dev/commands/available"?this.handleCommandsAvailable(a):n==="_kiro.dev/compaction/status"?this.emit("compactionStatus",a):n==="_kiro.dev/clear/status"&&this.emit("clearStatus",a)},(n,a,d)=>{n==="session/request_permission"?this.handlePermissionRequest(a,d):n.startsWith("cursor/")?this.transport.respondSuccess(a,{}).catch(()=>{}):this.transport.respondError(a,-32601,"method not implemented").catch(()=>{})});const t=await this.initialize();this.listSupported=!!t.agentCapabilities?.sessionCapabilities?.list,this.loadSupported=!!t.agentCapabilities?.loadSession;const s=t.agentCapabilities??{};if(this._supportsCommandsExecute=!!(s.extensions?.["_kiro.dev/commands"]||s.commands),e.authMethod)try{await this.transport.call("authenticate",{methodId:e.authMethod})}catch(n){throw h(n)?new p(m(n)):n}const i=e.sessionId&&e.sessionId!=="__continue__";let o=!1;if(i){if(!this.loadSupported)throw new Error(`session/load not supported by agent, cannot resume session ${e.sessionId}`);try{await this.loadSession(e.sessionId,e)&&(o=!0)}catch(n){throw h(n)?new p(m(n)):n}if(!o)throw new Error(`session/load failed for session ${e.sessionId}`)}else try{await this.newSession(e)}catch(n){throw h(n)?new p(m(n)):n}e.initialMode&&await this.setLiveMode(e.initialMode).catch(()=>{}),e.initialModel&&await this.setModel(e.initialModel).catch(()=>{}),this.alive=!0}async initialize(){return await this.transport.call("initialize",{protocolVersion:1,clientCapabilities:{fs:{readTextFile:!1,writeTextFile:!1},terminal:!1},clientInfo:{name:"grix-connector-acp",version:"0.2.0"}})}buildMcpEntries(e){return(e??[]).map(t=>{if("type"in t){const i={type:t.type,name:t.name,url:t.url};return t.headers&&(i.headers=Object.entries(t.headers).map(([o,n])=>({name:o,value:n}))),i}const s={name:t.name,command:t.command};return t.args&&(s.args=t.args),t.env&&(s.env=Object.entries(t.env).map(([i,o])=>({name:i,value:o}))),s})}async newSession(e){const t=this.buildMcpEntries(e.mcpServers),s={cwd:e.cwd||process.cwd(),mcpServers:t};e.additionalDirectories&&e.additionalDirectories.length>0&&(s.additionalDirectories=e.additionalDirectories);const i=await this.transport.call("session/new",s);if(!i?.sessionId)throw new Error("session/new returned empty sessionId");this.acpSessionId=i.sessionId,this.absorbModes(i.modes),r.info("acp-client",`[handshake] session/new models block: ${JSON.stringify(i.models)}`),this.absorbModels(i.models),this.absorbConfigOptions(i.configOptions)}async loadSession(e,t){const s=this.buildMcpEntries(t.mcpServers),i={sessionId:e,cwd:t.cwd||process.cwd(),mcpServers:s};t.additionalDirectories&&t.additionalDirectories.length>0&&(i.additionalDirectories=t.additionalDirectories);const o=await this.transport.call("session/load",i);return o?.sessionId?(this.acpSessionId=o.sessionId,this.absorbModes(o.modes),r.info("acp-client",`[handshake] session/load models block: ${JSON.stringify(o.models)}`),this.absorbModels(o.models),this.absorbConfigOptions(o.configOptions),!0):o&&(o.modes||o.models||o.configOptions)?(this.acpSessionId=e,this.absorbModes(o.modes),r.info("acp-client",`[handshake] session/load (compat) models block: ${JSON.stringify(o.models)}`),this.absorbModels(o.models),this.absorbConfigOptions(o.configOptions),!0):(r.info("acp-client",`[handshake] session/load returned empty result for ${e}`),this.acpSessionId=e,!0)}absorbModes(e){e?.availableModes?.length&&(this.availableModes=[...e.availableModes],e.currentModeId&&(this.currentMode=e.currentModeId))}absorbConfigOptions(e){if(Array.isArray(e))for(const t of e){const s=t,i=Array.isArray(s?.options)?s.options.filter(n=>typeof n?.value=="string"&&n.value):[];if(i.length===0)continue;const o=s.category||s.id;o==="mode"&&this.availableModes.length===0?(this.availableModes=i.map(n=>({id:n.value,name:n.name||n.value,...n.description?{description:n.description}:{}})),typeof s.currentValue=="string"&&s.currentValue&&(this.currentMode=s.currentValue),r.info("acp-client",`[handshake] absorbConfigOptions modes: count=${this.availableModes.length} ids=${JSON.stringify(this.availableModes.map(n=>n.id))} current=${s.currentValue??"<none>"}`)):o==="model"&&this.availableModels.length===0&&(this.availableModels=i.map(n=>({modelId:n.value,name:n.name||n.value})),typeof s.currentValue=="string"&&s.currentValue&&(this.currentModel=s.currentValue),r.info("acp-client",`[handshake] absorbConfigOptions models: count=${this.availableModels.length} ids=${JSON.stringify(this.availableModels.map(n=>n.modelId))} current=${s.currentValue??"<none>"}`))}}absorbModels(e){if(!e?.availableModels?.length){r.info("acp-client","[handshake] absorbModels skipped: block empty");return}this.availableModels=[...e.availableModels],r.info("acp-client",`[handshake] absorbModels: count=${this.availableModels.length} ids=${JSON.stringify(this.availableModels.map(t=>t.modelId))} current=${e.currentModelId??"<none>"}`),e.currentModelId&&(this.currentModel=e.currentModelId)}async send(e,t,s){if(!this.alive)throw new Error("session not active");if(!this.acpSessionId)throw new Error("no agent session id");const i=[{type:"text",text:e}];if(t)for(const o of t)i.push({type:"image",data:o.data.toString("base64"),mimeType:o.mimeType});this.pendingToolCallIds.clear(),this.settleSuppressedSince=0,await this.transport.call("session/prompt",{sessionId:this.acpSessionId,prompt:i,...this.currentModel?{modelId:this.currentModel}:{}}),this.scheduleSettledResult()}scheduleSettledResult(){this.settleTimer&&clearTimeout(this.settleTimer),this.settleTimer=setTimeout(()=>{if(this.settleTimer=null,!!this.alive){if(this.pendingToolCallIds.size>0){if(this.settleSuppressedSince===0&&(this.settleSuppressedSince=Date.now()),Date.now()-this.settleSuppressedSince<u.SETTLE_SUPPRESS_MAX_MS){r.info("acp-client",`settle suppressed: ${this.pendingToolCallIds.size} pending tool call(s) for session ${this.acpSessionId}`),this.scheduleSettledResult();return}r.warn("acp-client",`settle suppression exceeded ${u.SETTLE_SUPPRESS_MAX_MS/6e4}min with ${this.pendingToolCallIds.size} pending tool call(s), emitting Result anyway`)}this.settleSuppressedSince=0,r.info("acp-client",`settle timer fired, emitting Result for session ${this.acpSessionId}`),this.emit("event",{type:c.Result,sessionId:this.acpSessionId,done:!0,synthetic:!0})}},u.SETTLE_MS),this.settleTimer.unref()}deferSettledResult(){this.scheduleSettledResult()}clearSettleTimer(){this.settleTimer&&(clearTimeout(this.settleTimer),this.settleTimer=null),this.settleSuppressedSince=0}async cancel(){if(this.acpSessionId){this.pendingToolCallIds.clear(),this.settleSuppressedSince=0;try{await this.transport.notify("session/cancel",{sessionId:this.acpSessionId})}catch{}}}async authenticate(e){await this.transport.call("authenticate",{methodId:e})}async respondPermission(e,t){const s=this.pendingPermissions.get(e);if(!s)throw new Error(`unknown permission request: ${e}`);this.pendingPermissions.delete(e);const i=this.pickPermissionOptionId(t.behavior,s.options),o=this.buildPermissionResult(t.behavior,i);await this.transport.respondSuccess(s.rpcId,o)}async respondPermissionOption(e,t){const s=this.pendingPermissions.get(e);if(!s)throw new Error(`unknown permission request: ${e}`);if(!s.options.some(i=>i.optionId===t))throw new Error(`unknown permission option: ${t}`);this.pendingPermissions.delete(e),await this.transport.respondSuccess(s.rpcId,{outcome:{outcome:"selected",optionId:t}})}async ping(e=1e4){if(!this.alive)return!1;try{const t=new AbortController,s=setTimeout(()=>t.abort(),e);return await this.transport.call("session/list",{},t.signal),clearTimeout(s),!0}catch(t){return t?.code===-32601}}async setLiveMode(e){if(!this.acpSessionId)return r.warn("acp-client",`setLiveMode("${e}") skipped: no active session`),!1;const t=this.matchAvailableMode(e);if(!t)return r.warn("acp-client",`setLiveMode("${e}") failed: mode not found in available [${this.availableModes.map(s=>s.id).join(",")}]`),!1;try{const s=new AbortController,i=setTimeout(()=>s.abort(),8e3);return await this.transport.call("session/set_mode",{sessionId:this.acpSessionId,modeId:t},s.signal),clearTimeout(i),this.currentMode=t,!0}catch(s){return r.warn("acp-client",`setLiveMode("${t}") RPC failed: ${s instanceof Error?s.message:s}`),!1}}async setModel(e){if(!this.acpSessionId)return r.warn("acp-client",`setModel("${e}") skipped: no active session`),!1;try{const t=new AbortController,s=setTimeout(()=>t.abort(),8e3);return await this.transport.call("session/set_model",{sessionId:this.acpSessionId,modelId:e},t.signal),clearTimeout(s),this.currentModel=e,!0}catch(t){return r.warn("acp-client",`setModel("${e}") RPC failed: ${t instanceof Error?t.message:t}`),!1}}handleSessionUpdate(e){this.emit("activity");const t=e?.update?.sessionUpdate,s=t==="usage_update";if(t==="tool_call"||t==="tool_call_update"){const o=e?.update??{},n=String(o.toolCallId??""),a=String(o.status??"").toLowerCase().trim();n&&(a==="completed"||a==="failed"?this.pendingToolCallIds.delete(n):t==="tool_call"&&this.pendingToolCallIds.add(n))}this.settleTimer&&!s&&this.scheduleSettledResult();const i=b(this.acpSessionId,e);for(const o of i)o.sessionId||(o.sessionId=this.acpSessionId),o.type===c.Result&&(this.pendingToolCallIds.clear(),this.settleTimer&&this.clearSettleTimer()),this.emit("event",o)}handleKiroMetadata(e){const s=e?.contextUsagePercentage;typeof s!="number"||!Number.isFinite(s)||this.emit("event",{type:c.ContextWindowUpdate,sessionId:this.acpSessionId,contextWindow:{usedPercentage:Math.min(100,Math.max(0,s))}})}handleCommandsAvailable(e){const t=e,s=Array.isArray(t?.commands)?t.commands:[];this._availableCommands=s.map(i=>({name:String(i.name??""),description:i.description?String(i.description):void 0,args:i.args?String(i.args):void 0})),this._supportsCommandsExecute=!0,this.emit("commandsAvailable",this._availableCommands)}async executeCommand(e,t){if(!this.alive)throw new Error("session not active");if(!this.acpSessionId)throw new Error("no agent session id");const s=new AbortController,i=setTimeout(()=>s.abort(),2e4);try{const o=await this.transport.call("_kiro.dev/commands/execute",{sessionId:this.acpSessionId,command:e,...t?{args:t}:{}},s.signal);return clearTimeout(i),{status:o?.status??"ok",message:o?.message,options:o?.options,data:o?.data}}catch(o){clearTimeout(i);const n=o instanceof Error?o.message:String(o);if(o?.code===-32601)throw this._supportsCommandsExecute=!1,o;return{status:"failed",message:n}}}handlePermissionRequest(e,t){const s=t,i=s?.toolCall??{},o=String(e),n=Array.isArray(s?.options)?s.options:[];this.pendingPermissions.set(o,{rpcId:e,options:n});const a=i.title||i.kind||"permission",d=i.toolCallId||o,f=i.title||"",w={type:c.PermissionRequest,requestId:o,toolName:a,toolInput:f||d,sessionId:this.acpSessionId,permissionRequest:{requestId:o,toolCallId:d,toolName:a,toolTitle:f,options:n,rawParams:t}};this.emit("event",w)}matchAvailableMode(e){const t=e.toLowerCase();for(const s of this.availableModes)if(s.id.toLowerCase()===t||s.name.toLowerCase()===t)return s.id;return""}pickPermissionOptionId(e,t){if(t.length===0)return"";if(e==="deny"){for(const i of t)if(i.kind==="reject_once"||i.kind==="reject_always")return i.optionId;for(const i of t)if(i.kind.toLowerCase().includes("reject")||i.kind.toLowerCase().includes("deny"))return i.optionId;for(const i of t)if(i.name.toLowerCase().includes("reject")||i.name.toLowerCase().includes("deny"))return i.optionId;return t[t.length-1].optionId}const s=e==="allow-always"?"allow_always":e==="allow-once"?"allow_once":null;if(s){for(const i of t)if(i.kind===s)return i.optionId}for(const i of t)if(i.kind.toLowerCase().includes("allow"))return i.optionId;for(const i of t)if(i.name.toLowerCase().includes("allow"))return i.optionId;return t[0].optionId}buildPermissionResult(e,t){return e==="deny"?t?{outcome:{outcome:"selected",optionId:t}}:{outcome:{outcome:"cancelled"}}:t?{outcome:{outcome:"selected",optionId:t}}:{outcome:{outcome:"cancelled"}}}}export{p as AcpAuthRequiredError,u as AcpClient,h as isAuthRequiredError};
1
+ import{EventEmitter as S}from"events";import{log as r}from"../core/log/index.js";import{AgentEventType as u}from"../types/events.js";import{mapSessionUpdate as b}from"./event-mapper.js";import{AcpTerminalHost as M,acpTerminalErrorCode as v}from"./acp-terminal.js";class p extends Error{authMethods;constructor(e){super("ACP authentication required"),this.name="AcpAuthRequiredError",this.authMethods=e}}function m(l){if(!l||typeof l!="object")return!1;const e=l;return e.code===-32e3?!0:e.code===-32603&&e.data?.details?/\b(401|token\s*expired|access\s*token)\b/i.test(e.data.details):!1}function f(l){return!l||typeof l!="object"?[{id:"oauth"}]:l.data?.authMethods??[{id:"oauth"}]}class h extends S{acpSessionId="";alive=!1;pendingPermissions=new Map;availableModes=[];availableModels=[];currentMode="";currentModel="";listSupported=!1;loadSupported=!1;_supportsCommandsExecute=!1;_availableCommands=[];transport;terminalEnabled=!1;terminalHost=new M;settleTimer=null;static SETTLE_MS=3e3;pendingToolCallIds=new Set;settleSuppressedSince=0;static SETTLE_SUPPRESS_MAX_MS=900*1e3;constructor(){super(),this.transport=null}get sessionId(){return this.acpSessionId}get isAlive(){return this.alive}get modes(){return[...this.availableModes]}get mode(){return this.currentMode}get models(){return[...this.availableModels]}get model(){return this.currentModel}get sessionOptions(){return{modes:[...this.availableModes],currentModeId:this.currentMode,models:[...this.availableModels],currentModelId:this.currentModel}}get supportsCommandsExecute(){return this._supportsCommandsExecute}get availableCommands(){return[...this._availableCommands]}async connect(e){this.transport=e.transport,this.terminalEnabled=e.terminal===!0,this.transport.on("close",()=>{this.terminalHost.dispose(),this.alive&&(this.alive=!1,this.emit("session-lost"))}),this.transport.setHandlers((o,a)=>{o==="session/update"?this.handleSessionUpdate(a):o==="_kiro.dev/metadata"?this.handleKiroMetadata(a):o==="_kiro.dev/commands/available"?this.handleCommandsAvailable(a):o==="_kiro.dev/compaction/status"?this.emit("compactionStatus",a):o==="_kiro.dev/clear/status"&&this.emit("clearStatus",a)},(o,a,c)=>{o==="session/request_permission"?this.handlePermissionRequest(a,c):o.startsWith("terminal/")&&this.terminalEnabled?this.handleTerminalRequest(o,a,c).catch(d=>{r.warn("acp-client",`terminal response failed: ${d instanceof Error?d.message:String(d)}`)}):o.startsWith("cursor/")?this.transport.respondSuccess(a,{}).catch(()=>{}):this.transport.respondError(a,-32601,"method not implemented").catch(()=>{})});const t=await this.initialize();this.listSupported=!!t.agentCapabilities?.sessionCapabilities?.list,this.loadSupported=!!t.agentCapabilities?.loadSession;const i=t.agentCapabilities??{};if(this._supportsCommandsExecute=!!(i.extensions?.["_kiro.dev/commands"]||i.commands),e.authMethod)try{await this.transport.call("authenticate",{methodId:e.authMethod})}catch(o){throw m(o)?new p(f(o)):o}const s=e.sessionId&&e.sessionId!=="__continue__";let n=!1;if(s){if(!this.loadSupported)throw new Error(`session/load not supported by agent, cannot resume session ${e.sessionId}`);try{await this.loadSession(e.sessionId,e)&&(n=!0)}catch(o){throw m(o)?new p(f(o)):o}if(!n)throw new Error(`session/load failed for session ${e.sessionId}`)}else try{await this.newSession(e)}catch(o){throw m(o)?new p(f(o)):o}e.initialMode&&await this.setLiveMode(e.initialMode).catch(()=>{}),e.initialModel&&await this.setModel(e.initialModel).catch(()=>{}),this.alive=!0}async initialize(){return await this.transport.call("initialize",{protocolVersion:1,clientCapabilities:{fs:{readTextFile:!1,writeTextFile:!1},terminal:this.terminalEnabled},clientInfo:{name:"grix-connector-acp",version:"0.2.0"}})}buildMcpEntries(e){return(e??[]).map(t=>{if("type"in t){const s={type:t.type,name:t.name,url:t.url};return t.headers&&(s.headers=Object.entries(t.headers).map(([n,o])=>({name:n,value:o}))),s}const i={name:t.name,command:t.command};return t.args&&(i.args=t.args),t.env&&(i.env=Object.entries(t.env).map(([s,n])=>({name:s,value:n}))),i})}async newSession(e){const t=this.buildMcpEntries(e.mcpServers),i={cwd:e.cwd||process.cwd(),mcpServers:t};e.additionalDirectories&&e.additionalDirectories.length>0&&(i.additionalDirectories=e.additionalDirectories);const s=await this.transport.call("session/new",i);if(!s?.sessionId)throw new Error("session/new returned empty sessionId");this.acpSessionId=s.sessionId,this.absorbModes(s.modes),r.info("acp-client",`[handshake] session/new models block: ${JSON.stringify(s.models)}`),this.absorbModels(s.models),this.absorbConfigOptions(s.configOptions)}async loadSession(e,t){const i=this.buildMcpEntries(t.mcpServers),s={sessionId:e,cwd:t.cwd||process.cwd(),mcpServers:i};t.additionalDirectories&&t.additionalDirectories.length>0&&(s.additionalDirectories=t.additionalDirectories);const n=await this.transport.call("session/load",s);return n?.sessionId?(this.acpSessionId=n.sessionId,this.absorbModes(n.modes),r.info("acp-client",`[handshake] session/load models block: ${JSON.stringify(n.models)}`),this.absorbModels(n.models),this.absorbConfigOptions(n.configOptions),!0):n&&(n.modes||n.models||n.configOptions)?(this.acpSessionId=e,this.absorbModes(n.modes),r.info("acp-client",`[handshake] session/load (compat) models block: ${JSON.stringify(n.models)}`),this.absorbModels(n.models),this.absorbConfigOptions(n.configOptions),!0):(r.info("acp-client",`[handshake] session/load returned empty result for ${e}`),this.acpSessionId=e,!0)}absorbModes(e){e?.availableModes?.length&&(this.availableModes=[...e.availableModes],e.currentModeId&&(this.currentMode=e.currentModeId))}absorbConfigOptions(e){if(Array.isArray(e))for(const t of e){const i=t,s=Array.isArray(i?.options)?i.options.filter(o=>typeof o?.value=="string"&&o.value):[];if(s.length===0)continue;const n=i.category||i.id;n==="mode"&&this.availableModes.length===0?(this.availableModes=s.map(o=>({id:o.value,name:o.name||o.value,...o.description?{description:o.description}:{}})),typeof i.currentValue=="string"&&i.currentValue&&(this.currentMode=i.currentValue),r.info("acp-client",`[handshake] absorbConfigOptions modes: count=${this.availableModes.length} ids=${JSON.stringify(this.availableModes.map(o=>o.id))} current=${i.currentValue??"<none>"}`)):n==="model"&&this.availableModels.length===0&&(this.availableModels=s.map(o=>({modelId:o.value,name:o.name||o.value})),typeof i.currentValue=="string"&&i.currentValue&&(this.currentModel=i.currentValue),r.info("acp-client",`[handshake] absorbConfigOptions models: count=${this.availableModels.length} ids=${JSON.stringify(this.availableModels.map(o=>o.modelId))} current=${i.currentValue??"<none>"}`))}}absorbModels(e){if(!e?.availableModels?.length){r.info("acp-client","[handshake] absorbModels skipped: block empty");return}this.availableModels=[...e.availableModels],r.info("acp-client",`[handshake] absorbModels: count=${this.availableModels.length} ids=${JSON.stringify(this.availableModels.map(t=>t.modelId))} current=${e.currentModelId??"<none>"}`),e.currentModelId&&(this.currentModel=e.currentModelId)}async send(e,t,i){if(!this.alive)throw new Error("session not active");if(!this.acpSessionId)throw new Error("no agent session id");const s=[{type:"text",text:e}];if(t)for(const n of t)s.push({type:"image",data:n.data.toString("base64"),mimeType:n.mimeType});this.pendingToolCallIds.clear(),this.settleSuppressedSince=0,await this.transport.call("session/prompt",{sessionId:this.acpSessionId,prompt:s,...this.currentModel?{modelId:this.currentModel}:{}}),this.scheduleSettledResult()}scheduleSettledResult(){this.settleTimer&&clearTimeout(this.settleTimer),this.settleTimer=setTimeout(()=>{if(this.settleTimer=null,!!this.alive){if(this.pendingToolCallIds.size>0){if(this.settleSuppressedSince===0&&(this.settleSuppressedSince=Date.now()),Date.now()-this.settleSuppressedSince<h.SETTLE_SUPPRESS_MAX_MS){r.info("acp-client",`settle suppressed: ${this.pendingToolCallIds.size} pending tool call(s) for session ${this.acpSessionId}`),this.scheduleSettledResult();return}r.warn("acp-client",`settle suppression exceeded ${h.SETTLE_SUPPRESS_MAX_MS/6e4}min with ${this.pendingToolCallIds.size} pending tool call(s), emitting Result anyway`)}this.settleSuppressedSince=0,r.info("acp-client",`settle timer fired, emitting Result for session ${this.acpSessionId}`),this.emit("event",{type:u.Result,sessionId:this.acpSessionId,done:!0,synthetic:!0})}},h.SETTLE_MS),this.settleTimer.unref()}deferSettledResult(){this.scheduleSettledResult()}clearSettleTimer(){this.settleTimer&&(clearTimeout(this.settleTimer),this.settleTimer=null),this.settleSuppressedSince=0}dispose(){this.clearSettleTimer(),this.terminalHost.dispose(),this.alive=!1}async handleTerminalRequest(e,t,i){try{const s=await this.terminalHost.handle(e,i,this.acpSessionId);await this.transport.respondSuccess(t,s)}catch(s){await this.transport.respondError(t,v(s),s instanceof Error?s.message:String(s))}}async cancel(){if(this.acpSessionId){this.pendingToolCallIds.clear(),this.settleSuppressedSince=0;try{await this.transport.notify("session/cancel",{sessionId:this.acpSessionId})}catch{}}}async authenticate(e){await this.transport.call("authenticate",{methodId:e})}async respondPermission(e,t){const i=this.pendingPermissions.get(e);if(!i)throw new Error(`unknown permission request: ${e}`);this.pendingPermissions.delete(e);const s=this.pickPermissionOptionId(t.behavior,i.options),n=this.buildPermissionResult(t.behavior,s);await this.transport.respondSuccess(i.rpcId,n)}async respondPermissionOption(e,t){const i=this.pendingPermissions.get(e);if(!i)throw new Error(`unknown permission request: ${e}`);if(!i.options.some(s=>s.optionId===t))throw new Error(`unknown permission option: ${t}`);this.pendingPermissions.delete(e),await this.transport.respondSuccess(i.rpcId,{outcome:{outcome:"selected",optionId:t}})}async ping(e=1e4){if(!this.alive)return!1;try{const t=new AbortController,i=setTimeout(()=>t.abort(),e);return await this.transport.call("session/list",{},t.signal),clearTimeout(i),!0}catch(t){return t?.code===-32601}}async setLiveMode(e){if(!this.acpSessionId)return r.warn("acp-client",`setLiveMode("${e}") skipped: no active session`),!1;const t=this.matchAvailableMode(e);if(!t)return r.warn("acp-client",`setLiveMode("${e}") failed: mode not found in available [${this.availableModes.map(i=>i.id).join(",")}]`),!1;try{const i=new AbortController,s=setTimeout(()=>i.abort(),8e3);return await this.transport.call("session/set_mode",{sessionId:this.acpSessionId,modeId:t},i.signal),clearTimeout(s),this.currentMode=t,!0}catch(i){return r.warn("acp-client",`setLiveMode("${t}") RPC failed: ${i instanceof Error?i.message:i}`),!1}}async setModel(e){if(!this.acpSessionId)return r.warn("acp-client",`setModel("${e}") skipped: no active session`),!1;try{const t=new AbortController,i=setTimeout(()=>t.abort(),8e3);return await this.transport.call("session/set_model",{sessionId:this.acpSessionId,modelId:e},t.signal),clearTimeout(i),this.currentModel=e,!0}catch(t){return r.warn("acp-client",`setModel("${e}") RPC failed: ${t instanceof Error?t.message:t}`),!1}}handleSessionUpdate(e){this.emit("activity");const t=e?.update?.sessionUpdate,i=t==="usage_update";if(t==="tool_call"||t==="tool_call_update"){const n=e?.update??{},o=String(n.toolCallId??""),a=String(n.status??"").toLowerCase().trim();o&&(a==="completed"||a==="failed"?this.pendingToolCallIds.delete(o):t==="tool_call"&&this.pendingToolCallIds.add(o))}this.settleTimer&&!i&&this.scheduleSettledResult();const s=b(this.acpSessionId,e);for(const n of s)n.sessionId||(n.sessionId=this.acpSessionId),n.type===u.Result&&(this.pendingToolCallIds.clear(),this.settleTimer&&this.clearSettleTimer()),this.emit("event",n)}handleKiroMetadata(e){const i=e?.contextUsagePercentage;typeof i!="number"||!Number.isFinite(i)||this.emit("event",{type:u.ContextWindowUpdate,sessionId:this.acpSessionId,contextWindow:{usedPercentage:Math.min(100,Math.max(0,i))}})}handleCommandsAvailable(e){const t=e,i=Array.isArray(t?.commands)?t.commands:[];this._availableCommands=i.map(s=>({name:String(s.name??""),description:s.description?String(s.description):void 0,args:s.args?String(s.args):void 0})),this._supportsCommandsExecute=!0,this.emit("commandsAvailable",this._availableCommands)}async executeCommand(e,t){if(!this.alive)throw new Error("session not active");if(!this.acpSessionId)throw new Error("no agent session id");const i=new AbortController,s=setTimeout(()=>i.abort(),2e4);try{const n=await this.transport.call("_kiro.dev/commands/execute",{sessionId:this.acpSessionId,command:e,...t?{args:t}:{}},i.signal);return clearTimeout(s),{status:n?.status??"ok",message:n?.message,options:n?.options,data:n?.data}}catch(n){clearTimeout(s);const o=n instanceof Error?n.message:String(n);if(n?.code===-32601)throw this._supportsCommandsExecute=!1,n;return{status:"failed",message:o}}}handlePermissionRequest(e,t){const i=t,s=i?.toolCall??{},n=String(e),o=Array.isArray(i?.options)?i.options:[];this.pendingPermissions.set(n,{rpcId:e,options:o});const a=s.title||s.kind||"permission",c=s.toolCallId||n,d=s.title||"",w={type:u.PermissionRequest,requestId:n,toolName:a,toolInput:d||c,sessionId:this.acpSessionId,permissionRequest:{requestId:n,toolCallId:c,toolName:a,toolTitle:d,options:o,rawParams:t}};this.emit("event",w)}matchAvailableMode(e){const t=e.toLowerCase();for(const i of this.availableModes)if(i.id.toLowerCase()===t||i.name.toLowerCase()===t)return i.id;return""}pickPermissionOptionId(e,t){if(t.length===0)return"";if(e==="deny"){for(const s of t)if(s.kind==="reject_once"||s.kind==="reject_always")return s.optionId;for(const s of t)if(s.kind.toLowerCase().includes("reject")||s.kind.toLowerCase().includes("deny"))return s.optionId;for(const s of t)if(s.name.toLowerCase().includes("reject")||s.name.toLowerCase().includes("deny"))return s.optionId;return t[t.length-1].optionId}const i=e==="allow-always"?"allow_always":e==="allow-once"?"allow_once":null;if(i){for(const s of t)if(s.kind===i)return s.optionId}for(const s of t)if(s.kind.toLowerCase().includes("allow"))return s.optionId;for(const s of t)if(s.name.toLowerCase().includes("allow"))return s.optionId;return t[0].optionId}buildPermissionResult(e,t){return e==="deny"?t?{outcome:{outcome:"selected",optionId:t}}:{outcome:{outcome:"cancelled"}}:t?{outcome:{outcome:"selected",optionId:t}}:{outcome:{outcome:"cancelled"}}}}export{p as AcpAuthRequiredError,h as AcpClient,m as isAuthRequiredError};
@@ -0,0 +1,2 @@
1
+ import{randomUUID as p}from"node:crypto";import{killProcessGroup as d,resolveCommandPath as w,spawnCommand as y}from"../core/runtime/spawn.js";const g=4*1024*1024,L=16*1024*1024;class s extends Error{code;constructor(t,e=-32602){super(t),this.code=e}}class A{id;process;outputByteLimit;output=Buffer.alloc(0);truncated=!1;exitCode=null;signal=null;settled=!1;exitPromise;resolveExit;constructor(t,e,i){this.id=t,this.process=e,this.outputByteLimit=i,this.exitPromise=new Promise(n=>{this.resolveExit=n}),e.stdout?.on("data",n=>this.appendOutput(n)),e.stderr?.on("data",n=>this.appendOutput(n)),e.once("error",n=>{this.appendOutput(Buffer.from(`${n.message}
2
+ `,"utf8")),this.settle(-1,null)}),e.once("close",(n,o)=>this.settle(n,o))}async waitUntilSpawned(){this.process.pid||await new Promise((t,e)=>{const i=()=>{o(),t()},n=a=>{o(),e(a)},o=()=>{this.process.removeListener("spawn",i),this.process.removeListener("error",n)};this.process.once("spawn",i),this.process.once("error",n)})}currentOutput(){return{output:this.output.toString("utf8"),truncated:this.truncated,exitStatus:{exitCode:this.exitCode,signal:this.signal}}}waitForExit(){return this.exitPromise}kill(){this.settled||d(this.process,"SIGKILL")}release(){this.settled||d(this.process,"SIGKILL")}appendOutput(t){if(this.output.length>=this.outputByteLimit){this.truncated=!0;return}const e=Buffer.isBuffer(t)?t:Buffer.from(t),i=this.outputByteLimit-this.output.length;e.length>i&&(this.truncated=!0),this.output=Buffer.concat([this.output,e.subarray(0,i)])}settle(t,e){this.settled||(this.settled=!0,this.exitCode=t,this.signal=e,this.resolveExit({exitCode:t,signal:e}))}}class v{terminals=new Map;async handle(t,e,i){const n=x(e),o=l(n,"sessionId");if(!i||o!==i)throw new s("terminal request session does not match the active ACP session");if(t==="terminal/create")return this.create(n);const a=l(n,"terminalId"),u=this.terminals.get(a);if(!u)throw new s(`unknown terminal: ${a}`);if(t==="terminal/output")return u.currentOutput();if(t==="terminal/wait_for_exit")return u.waitForExit();if(t==="terminal/kill")return u.kill(),{};if(t==="terminal/release")return u.release(),this.terminals.delete(a),{};throw new s(`unsupported terminal method: ${t}`,-32601)}dispose(){for(const t of this.terminals.values())t.release();this.terminals.clear()}async create(t){const e=l(t,"command");if(!e)throw new s("terminal command is required");const i=B(t.args,"args"),n=t.cwd==null?void 0:l(t,"cwd"),o={...process.env,...T(t.env)},a=typeof t.outputByteLimit=="number"&&Number.isFinite(t.outputByteLimit)?t.outputByteLimit:g,u=Math.max(1,Math.min(Math.trunc(a),L)),f=w(e,typeof o.PATH=="string"?o.PATH:void 0),h=y(f,i,{cwd:n,env:o}).process,c=new A(p(),h,u);this.terminals.set(c.id,c);try{await c.waitUntilSpawned()}catch(m){throw c.release(),this.terminals.delete(c.id),new s(`failed to start terminal command: ${m instanceof Error?m.message:String(m)}`,-32603)}return{terminalId:c.id}}}function I(r){return r instanceof s?r.code:-32603}function x(r){if(!r||typeof r!="object"||Array.isArray(r))throw new s("terminal params must be an object");return r}function l(r,t){const e=r[t];if(typeof e!="string")throw new s(`${t} must be a string`);return e}function B(r,t){if(r===void 0)return[];if(!Array.isArray(r)||r.some(e=>typeof e!="string"))throw new s(`${t} must be an array of strings`);return[...r]}function T(r){if(r===void 0)return{};if(!Array.isArray(r))throw new s("env must be an array");const t={};for(const e of r){if(!e||typeof e!="object"||Array.isArray(e))throw new s("env entries must be objects");const i=e;if(typeof i.name!="string"||typeof i.value!="string")throw new s("env entries require string name and value");t[i.name]=i.value}return t}export{v as AcpTerminalHost,I as acpTerminalErrorCode};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grix-connector",
3
- "version": "4.0.0",
3
+ "version": "4.0.1",
4
4
  "description": "Connect local AI coding agents (Claude, Codex, Gemini, Qwen, DeepSeek, Cursor, OpenCode, Pi, OpenHuman, Reasonix) to the Grix scheduling platform. Also serves as an OpenClaw plugin for Grix channel transport.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1 +0,0 @@
1
- import c from"node:http";import{randomUUID as d}from"node:crypto";import{log as o}from"../../core/log/index.js";function l(t){t.writeHead(401,{"content-type":"application/json"}),t.end(JSON.stringify({error:"unauthorized"}))}function u(t){t.writeHead(404,{"content-type":"application/json"}),t.end(JSON.stringify({error:"not_found"}))}function h(t,e){t.writeHead(400,{"content-type":"application/json"}),t.end(JSON.stringify({error:e}))}function p(t,e={ok:!0}){t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify(e))}async function v(t){const e=[];for await(const r of t)e.push(r);const n=Buffer.concat(e).toString("utf8").trim();return n?JSON.parse(n):{}}function k(t){const e=(t.headers.authorization??"").trim();return e.toLowerCase().startsWith("bearer ")?e.slice(7).trim():""}class w{host="127.0.0.1";port=0;token;callbacks;server=null;address=null;constructor(e){this.token=d(),this.callbacks=e}getToken(){return this.token}getURL(){return this.address?`http://${this.address.address}:${this.address.port}`:""}async start(){this.server||(this.server=c.createServer(async(e,n)=>{try{await this.handleRequest(e,n)}catch(r){h(n,r instanceof Error?r.message:String(r))}}),await new Promise((e,n)=>{this.server.once("error",n),this.server.listen(this.port,this.host,()=>{this.server.off("error",n),e()})}),this.address=this.server.address(),o.info("claude-bridge",`Bridge server listening on ${this.getURL()}`))}async stop(){if(!this.server)return;const e=this.server;this.server=null,this.address=null,e.closeIdleConnections?.(),e.closeAllConnections?.(),await new Promise((n,r)=>{e.close(s=>s?r(s):n())})}async handleRequest(e,n){if(k(e)!==this.token){l(n);return}if(e.method!=="POST"){n.writeHead(405,{"content-type":"application/json"}),n.end(JSON.stringify({error:"method_not_allowed"}));return}const r=new URL(e.url,"http://localhost").pathname,s=await v(e),i=f.get(r);if(!i){u(n);return}const a=await i(this.callbacks,s);p(n,a??{ok:!0})}}const f=new Map([["/v1/worker/register",async(t,e)=>(o.info("claude-bridge",`Worker registered: ${e.worker_id} (pid=${e.pid})`),t.onRegisterWorker(e))],["/v1/worker/status",async(t,e)=>(o.info("claude-bridge",`Worker status: ${e.status}`),t.onStatusUpdate(e))],["/v1/worker/send-text",async(t,e)=>t.onSendText(e)],["/v1/worker/send-stream-chunk",async(t,e)=>t.onSendStreamChunk(e)],["/v1/worker/send-media",async(t,e)=>t.onSendMedia(e)],["/v1/worker/delete-message",async(t,e)=>t.onDeleteMessage(e)],["/v1/worker/ack-event",async(t,e)=>t.onAckEvent(e)],["/v1/worker/event-result",async(t,e)=>t.onSendEventResult(e)],["/v1/worker/event-stop-ack",async(t,e)=>t.onSendEventStopAck(e)],["/v1/worker/event-stop-result",async(t,e)=>t.onSendEventStopResult(e)],["/v1/worker/session-composing",async(t,e)=>t.onSetSessionComposing(e)],["/v1/worker/agent-invoke",async(t,e)=>t.onAgentInvoke(e)],["/v1/worker/local-action-result",async(t,e)=>t.onLocalActionResult(e)]]);export{w as ClaudeBridgeServer};
@@ -1 +0,0 @@
1
- import{randomUUID as x}from"node:crypto";import{CallToolRequestSchema as S,ListToolsRequestSchema as w}from"@modelcontextprotocol/sdk/types.js";import{log as f}from"../../core/log/index.js";import{toolCallToInvoke as k}from"../../core/mcp/tools.js";const E=new Set(["contact_search","session_search","message_history","message_search","group_create","group_detail_read","group_leave_self","group_member_add","group_member_remove","group_member_role_update","group_all_members_muted_update","group_member_speaking_update","group_dissolve","send_msg","delete_msg","agent_api_create","agent_category_list","agent_category_create","agent_category_update","agent_category_assign","agent_api_key_rotate"]),y=3e4,I=[{name:"reply",description:"Send a visible message back to the chat for this grix-claude event.",inputSchema:{type:"object",properties:{text:{type:"string",description:"The visible reply text to send."},chat_id:{type:"string",description:"The target chat/session id from the <channel> tag."},event_id:{type:"string",description:"The Aibot event_id from the <channel> tag."},reply_to:{type:"string",description:"Optional message_id to quote instead of the inbound trigger message."},files:{type:"array",items:{type:"string"},description:"Optional absolute local file paths. Each file is uploaded through Agent API OSS presign before sending."},final:{type:"boolean",description:"Whether this is the final reply for the event. Defaults to false \u2014 the event stays open while Claude continues working, and auto-completes after inactivity. Set true only when this is definitively the last message for the event."}},required:["chat_id","event_id"]}},{name:"complete",description:"Finish an event without sending a visible reply so the backend does not time out.",inputSchema:{type:"object",properties:{event_id:{type:"string",description:"The Aibot event_id from the <channel> tag."},status:{type:"string",enum:["responded","canceled","failed"]},code:{type:"string"},msg:{type:"string"}},required:["event_id","status"]}},{name:"delete_message",description:"Delete a previously sent message in the same grix-claude chat.",inputSchema:{type:"object",properties:{chat_id:{type:"string"},message_id:{type:"string"}},required:["chat_id","message_id"]}},{name:"status",description:"Show grix-claude runtime status, upstream access state, bridge health, and startup hints.",inputSchema:{type:"object",properties:{}}},{name:"send",description:"Send a message to a chat session proactively, without requiring an inbound event. Use for notifications or scheduled reports.",inputSchema:{type:"object",properties:{chat_id:{type:"string",description:"The target chat/session id."},text:{type:"string",description:"The message text to send."}},required:["chat_id","text"]}},{name:"access_pair",description:"Forward a sender pairing approval code to upstream access control.",inputSchema:{type:"object",properties:{code:{type:"string"}},required:["code"]}},{name:"access_deny",description:"Forward a sender pairing denial code to upstream access control.",inputSchema:{type:"object",properties:{code:{type:"string"}},required:["code"]}},{name:"allow_sender",description:"Ask upstream access control to allow a sender_id.",inputSchema:{type:"object",properties:{sender_id:{type:"string"}},required:["sender_id"]}},{name:"remove_sender",description:"Ask upstream access control to remove a sender_id.",inputSchema:{type:"object",properties:{sender_id:{type:"string"}},required:["sender_id"]}},{name:"access_policy",description:"Ask upstream access control to update the sender access policy.",inputSchema:{type:"object",properties:{policy:{type:"string",enum:["allowlist","open","disabled"]}},required:["policy"]}},{name:"grix_query",description:"Search contacts, sessions, message history, or messages by keyword in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["contact_search","session_search","message_history","message_search"]},keyword:{type:"string"},id:{type:"string"},sessionId:{type:"string"},limit:{type:"number"},offset:{type:"number"},beforeId:{type:"string"}},required:["action"]}},{name:"grix_group",description:"Manage groups in the Grix/AIBot platform: create, get details, leave, dissolve, manage members and permissions.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["create","detail","leave","add_members","remove_members","update_member_role","update_all_members_muted","update_member_speaking","dissolve"]},sessionId:{type:"string"},name:{type:"string"},memberIds:{type:"array",items:{type:"string"}},memberTypes:{type:"array",items:{type:"integer",enum:[1,2]}},memberId:{type:"string"},role:{type:"integer",enum:[1,2]},memberType:{type:"integer",description:"Member type (for update_member_role / update_member_speaking)."},allMembersMuted:{type:"boolean"},isSpeakMuted:{type:"boolean"},canSpeakWhenAllMuted:{type:"boolean",description:"Allow speaking when all muted (for update_member_speaking)."}},required:["action"]}},{name:"grix_message_send",description:"Send a message to a session in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{sessionId:{type:"string"},content:{type:"string"},msgType:{type:"number"},quotedMessageId:{type:"string"},threadId:{type:"string"}},required:["sessionId","content"]}},{name:"grix_message_unsend",description:"Recall/unsend a message in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{sessionId:{type:"string"},msgId:{type:"string"}},required:["sessionId","msgId"]}},{name:"grix_admin",description:"Agent and category management in the Grix/AIBot platform: create agents, manage categories, rotate API keys.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["create_agent","list_categories","create_category","update_category","assign_category","rotate_api_key"]},agentId:{type:"string"},agentName:{type:"string"},introduction:{type:"string"},isMain:{type:"boolean"},categoryId:{type:"string"},name:{type:"string"},parentId:{type:"string"},sortOrder:{type:"number"}},required:["action"]}}];function q(r,e){r.setRequestHandler(w,async()=>({tools:I})),r.setRequestHandler(S,async i=>{const{name:n,arguments:t}=i.params,s=t??{};try{switch(n){case"reply":return await A(s,e);case"complete":return await $(s,e);case"delete_message":return await T(s,e);case"status":return j(e);case"send":return await M(s,e);case"access_pair":case"access_deny":case"allow_sender":case"remove_sender":case"access_policy":return await R(n,s,e);case"grix_query":case"grix_group":case"grix_message_send":case"grix_message_unsend":case"grix_admin":return await O(n,s,e);default:return{content:[{type:"text",text:`Unknown tool: ${n}`}],isError:!0}}}catch(a){return f.error("claude-tools",`Tool ${n} error: ${a}`),{content:[{type:"text",text:`Error: ${a instanceof Error?a.message:String(a)}`}],isError:!0}}})}async function A(r,e){const i=e.getActiveEvent();if(!i)return{content:[{type:"text",text:"No active event to reply to"}],isError:!0};const n=String(r.text??""),t=String(r.chat_id??""),s=String(r.event_id??i.eventId),a=r.reply_to,d=r.files,_=r.final===!0;if(!t||!s)return{content:[{type:"text",text:"reply requires chat_id and event_id"}],isError:!0};if(!n.trim()&&(!d||d.length===0))return{content:[{type:"text",text:"reply requires at least one of text or files"}],isError:!0};const{text:g,quotedMessageId:v}=e.resolveQuotedMessageId(a,n),b=[];let m=0;const h=`reply_${s}_${Date.now()}`;try{if(g){const p=e.splitText(g);for(let o=0;o<p.length;o++){if(!e.isEventActive(s))return c("ignored: event no longer active");m++,e.bridge.sendStreamChunk(s,t,p[o],++i.chunkSeq,!1,h)}}if(d&&d.length>0)for(const p of d){if(!e.isEventActive(s))return c("ignored: event no longer active");m++;const o=await e.uploadFile(p,t),l=`${x()}_${m}`;e.bridge.sendMedia(s,t,o.access_url,o.file_name,v,l,o.extra),f.info("claude-tools",`File sent: ${o.file_name}`)}e.bridge.sendStreamChunk(s,t,"",++i.chunkSeq,!0,h)}catch(p){if(g&&b.length===0)try{const o=`fallback_${s}_${Date.now()}`,l=e.splitText(g);for(let u=0;u<l.length;u++)e.bridge.sendStreamChunk("",t,l[u],u+1,!1,o);return e.bridge.sendStreamChunk("",t,"",l.length+1,!0,o),e.markReplySent(s),_&&e.finalizeEvent(s,"responded"),c("sent via fallback")}catch{}if(!e.isEventActive(s))return c("ignored: event no longer active");throw e.bridge.sendEventResult(s,"failed",String(p),"send_msg_failed"),p}return e.markReplySent(s),_?e.finalizeEvent(s,"responded"):(i.responded=!0,e.clearActiveEvent("completed")),c("Reply sent")}async function $(r,e){const i=e.getActiveEvent(),n=String(r.event_id??""),t=r.status??"",s=r.code,a=r.msg;if(!n||!t)return{content:[{type:"text",text:"complete requires event_id and status"}],isError:!0};const d=["responded","canceled","failed"];return d.includes(t)?e.isEventActive(n)?(e.bridge.sendEventResult(n,t,a,s),e.clearActiveEvent(t),c("Event completed")):c("ignored: event no longer active"):{content:[{type:"text",text:`status must be one of: ${d.join(", ")}`}],isError:!0}}async function T(r,e){const i=String(r.chat_id??""),n=String(r.message_id??"");if(!i||!n)return{content:[{type:"text",text:"chat_id and message_id are required"}],isError:!0};try{return await e.bridge.agentInvoke("grix_message_unsend",{sessionId:i,msgId:n},y),c(`deleted (${n})`)}catch(t){return{content:[{type:"text",text:`Delete failed: ${t}`}],isError:!0}}}function j(r){const e=r.getStatusInfo();return{content:[{type:"text",text:JSON.stringify({alive:e.alive,active_event:e.activeEvent,pending_approvals:e.pendingPermissions,pending_questions:e.pendingElicitations})}]}}async function M(r,e){const i=String(r.chat_id??""),n=String(r.text??"");if(!i||!n)return{content:[{type:"text",text:"chat_id and text are required"}],isError:!0};try{const t=e.splitText(n),s=`send_${i}_${Date.now()}`;for(let a=0;a<t.length;a++)e.bridge.sendStreamChunk("",i,t[a],a+1,!1,s);return e.bridge.sendStreamChunk("",i,"",t.length+1,!0,s),c("sent")}catch(t){return{content:[{type:"text",text:`Send failed: ${t}`}],isError:!0}}}const C={access_pair:{verb:"pair_approve",payloadKey:"code"},access_deny:{verb:"pair_deny",payloadKey:"code"},allow_sender:{verb:"sender_allow",payloadKey:"sender_id"},remove_sender:{verb:"sender_remove",payloadKey:"sender_id"},access_policy:{verb:"policy_set",payloadKey:"policy"}};async function R(r,e,i){try{const n=C[r];if(!n)throw new Error(`Unknown access control tool: ${r}`);const t={};e.code!=null&&(t.code=e.code),e.sender_id!=null&&(t.sender_id=e.sender_id),e.policy!=null&&(t.policy=e.policy);const s=await i.bridge.agentInvoke("claude_access_control",{verb:n.verb,payload:t},y);return{content:[{type:"text",text:typeof s=="string"?s:JSON.stringify(s)}]}}catch(n){return{content:[{type:"text",text:`${r} failed: ${n}`}],isError:!0}}}async function O(r,e,i){try{const n=k(r,e);if(!E.has(n.action))throw new Error(`Action not allowed: ${n.action}`);const t=await i.bridge.agentInvoke(n.action,n.params,y);if(t&&Number(t.code??0)!==0)throw new Error(String(t.msg??"invoke failed"));return{content:[{type:"text",text:t?.data!=null?typeof t.data=="string"?t.data:JSON.stringify(t.data):JSON.stringify(t)}]}}catch(n){return{content:[{type:"text",text:`${r} failed: ${n}`}],isError:!0}}}function c(r){return{content:[{type:"text",text:r}]}}export{q as registerClaudeTools};
@@ -1 +0,0 @@
1
- import{log as l}from"../../core/log/index.js";class c{controlURL="";token="";isConfigured(){return!!(this.controlURL&&this.token)}configure(r,e){this.controlURL=r.replace(/\/+$/,""),this.token=e.trim(),l.info("claude-worker-client",`Configured with control URL: ${this.controlURL}`)}async post(r,e,s){if(!this.isConfigured())throw new Error("worker control not configured");const i=new AbortController,o=setTimeout(()=>i.abort(),s);try{const t=await fetch(`${this.controlURL}${r}`,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${this.token}`},body:JSON.stringify(e),signal:i.signal}),n=await t.text(),a=n.trim()?JSON.parse(n):{};if(!t.ok)throw new Error(a.error||`worker control failed ${t.status}`);return a}finally{clearTimeout(o)}}isRetryableError(r){const e=r instanceof Error?r.message:String(r);return/fetch failed|network|ECONNRESET|ETIMEDOUT|EAI_AGAIN|socket hang up|aborted/i.test(e)}async postWithRetry(r,e,s,i=1){let o;for(let t=0;t<=i;t++)try{return t>0&&l.info("claude-worker-client",`Retrying ${r} attempt=${t+1}`),await this.post(r,e,s)}catch(n){if(o=n,t>=i||!this.isRetryableError(n))break;await new Promise(a=>setTimeout(a,150))}throw o instanceof Error?o:new Error(String(o))}async deliverEvent(r){return this.postWithRetry("/v1/worker/deliver-event",{payload:r},1e4,1)}async deliverStop(r){return this.postWithRetry("/v1/worker/deliver-stop",{payload:r},1e4,1)}async deliverLocalAction(r){return this.postWithRetry("/v1/worker/deliver-local-action",{payload:r},1e4,1)}async ping(){try{return await this.postWithRetry("/v1/worker/ping",{},5e3,1),!0}catch{return!1}}}export{c as ClaudeWorkerClient};
@@ -1,2 +0,0 @@
1
- import{spawn as x,execSync as y}from"node:child_process";import{randomUUID as v}from"node:crypto";import{mkdir as S}from"node:fs/promises";import{readFileSync as C}from"node:fs";import{join as d}from"node:path";import{homedir as T,tmpdir as I}from"node:os";import{log as o}from"../../core/log/index.js";import{MCP_HTTP_CHANNEL_NAME as m}from"./protocol-contract.js";function P(t){let e=null,r=0,n=!1,i=!1;const a=v(),s=t.gatewayUrl??"http://127.0.0.1:19580/mcp";return{async start(){await F(t.command,s,t.env);const c=E(t.grix),u=[...t.args??[],"--name",`grix-mcp-${t.name}`,"--session-id",a];t.fullAuto&&u.push("--dangerously-skip-permissions"),u.push("--dangerously-load-development-channels",`server:${m}`,"--append-system-prompt",c);const f=d(I(),`grix-mcp-claude-${t.name}`);await S(f,{recursive:!0});const{expectPath:$,pidPath:g}=await M(f,t.command,u),_={...process.env,...t.env??{}};e=x("/usr/bin/expect",[$],{cwd:t.cwd,env:_,stdio:["ignore","pipe","pipe"],detached:!0}),o.info("mcp-http-launcher",`\u542F\u52A8 Claude: name=${t.name} cwd=${t.cwd} pid=${e.pid}`),r=await k(g),n=!0,o.info("mcp-http-launcher",`Claude \u5B50\u8FDB\u7A0B PID: ${r}`),e.on("exit",(l,p)=>{o.info("mcp-http-launcher",`Claude \u9000\u51FA: code=${l} signal=${p}`),n=!1,e=null,r=0,i||(o.info("mcp-http-launcher","3 \u79D2\u540E\u81EA\u52A8\u91CD\u542F..."),setTimeout(()=>{i||this.start().catch(w=>{o.error("mcp-http-launcher",`\u91CD\u542F\u5931\u8D25: ${w}`)})},3e3))}),e.stdout?.on("data",l=>{const p=l.toString().trim();p&&o.info("mcp-http-launcher",`[stdout] ${p.slice(0,300)}`)}),e.stderr?.on("data",l=>{const p=l.toString().trim();p&&o.info("mcp-http-launcher",`[stderr] ${p.slice(0,300)}`)})},async stop(){if(i=!0,n=!1,r>0)try{process.kill(r,"SIGTERM")}catch{}if(e?.pid){try{process.kill(-e.pid,"SIGTERM")}catch{}await new Promise(c=>{const u=setTimeout(()=>{if(r>0)try{process.kill(r,"SIGKILL")}catch{}if(e?.pid)try{process.kill(-e.pid,"SIGKILL")}catch{}c()},5e3);e?.once("exit",()=>{clearTimeout(u),c()})})}e=null,r=0},getStatus(){return{name:t.name,alive:n,pid:r}}}}function E(t){return["You are connected to a chat via the grix MCP server.",`On startup, immediately call grix_authorize with: agentId="${t.agentId}", apiKey="${t.apiKey}", wsUrl="${t.wsUrl}", clientType="${t.clientType}".`,"When you receive a <channel> message, you MUST respond by calling the grix_reply tool (or the grix_complete tool if no response is needed).","Never write your reply as plain text \u2014 it will NOT reach the user. Only the grix_reply tool delivers your response to the chat.","The <channel> message contains event_id and session_id \u2014 pass them to grix_reply."].join(" ")}async function F(t,e,r){const n=d(T(),".claude.json");let i=null;try{const s=C(n,"utf8");i=JSON.parse(s)?.mcpServers?.[m]??null}catch{}if(i&&String(i.type??"").trim()==="http"&&String(i.url??"").trim()===e)return;o.info("mcp-http-launcher",`\u6CE8\u518C MCP Server: ${m} -> ${e}`);const a={...process.env,...r??{}};try{y(`${t} mcp remove -s user ${m}`,{encoding:"utf8",timeout:1e4,env:a,stdio:"pipe"})}catch{}y(`${t} mcp add --scope user --transport http ${m} ${e}`,{encoding:"utf8",timeout:1e4,env:a,stdio:"pipe"})}async function M(t,e,r){const{writeFile:n}=await import("node:fs/promises"),i=d(t,"claude.pid"),a=d(t,"claude.expect"),s=["log_user 1","set timeout -1","set startup_prompt_armed 1",`set claude_command [list {${h(e)}}${r.map(c=>` {${h(c)}}`).join("")}]`,"spawn -noecho {*}$claude_command",`set pid_file [open {${h(i)}} w]`,"puts $pid_file [exp_pid -i $spawn_id]","close $pid_file","expect {"," -re {(?i)(Quick.*safety.*check|trust.*folder)} {",' if {$startup_prompt_armed} { send -- "1\\r"; after 300 }; exp_continue'," }"," -re {(?i)I am using this for local development} {",' if {$startup_prompt_armed} { send -- "1\\r"; after 300 }; exp_continue'," }"," -re {(?i)(Enter.*confirm|Press.*Enter|Hit.*Enter)} {",' if {$startup_prompt_armed} { send -- "\\r"; after 300 }; exp_continue'," }"," -re {Listening for channel} {"," set startup_prompt_armed 0"," after 1000",' send -- "Call grix_authorize now as instructed in your system prompt.\\r"'," }"," -re {bypass permissions} {"," set startup_prompt_armed 0"," after 1000",' send -- "Call grix_authorize now as instructed in your system prompt.\\r"'," }"," eof {}","}","expect eof",""];return await n(i,"","utf8"),await n(a,s.join(`
2
- `),"utf8"),{expectPath:a,pidPath:i}}function h(t){return t.replace(/[\\{}$\[\]"]/g,"\\$&")}async function k(t,e=1e4){const{readFile:r}=await import("node:fs/promises"),n=Math.ceil(e/100);for(let i=0;i<n;i++){try{const a=await r(t,"utf8"),s=parseInt(String(a).trim(),10);if(Number.isFinite(s)&&s>0)return s}catch{}await new Promise(a=>setTimeout(a,100))}return 0}export{P as createMcpHttpLauncher};
@@ -1 +0,0 @@
1
- class m{defaultTimeoutMs;onTimeout;timers=new Map;constructor(e){this.defaultTimeoutMs=e.defaultTimeoutMs??9e4,this.onTimeout=e.onTimeout}arm(e,t){this.cancel(e);const s=t?.timeoutMs??this.defaultTimeoutMs,i=Date.now()+s,o=setTimeout(()=>{this.timers.delete(e),this.onTimeout(e).catch(()=>{})},s);return this.timers.set(e,o),i}cancel(e){const t=this.timers.get(e);t&&(clearTimeout(t),this.timers.delete(e))}has(e){return this.timers.has(e)}close(){for(const e of this.timers.values())clearTimeout(e);this.timers.clear()}}export{m as ResultTimeoutManager};
@@ -1,6 +0,0 @@
1
- import{resolveCommandPath as f,spawnCommand as T}from"../../core/runtime/spawn.js";import{createInterface as w}from"node:readline";import{EventEmitter as I}from"node:events";import{formatInboundMessageReferenceText as _}from"../../core/protocol/message-reference.js";import{log as o}from"../../core/log/index.js";import{SessionBindingStore as E}from"../../core/persistence/session-binding-store.js";const S=120*1e3;class y extends I{type="deepseek";config;callbacks;alive=!1;stopped=!1;deepSeekSessionId=null;activeEventId=null;activeSessionId=null;chunkSeq=0;activeClientMsgId=null;idleTimer=null;activeProcess=null;composingTimer=null;composingTTLClear=null;composingTTL=12e4;composingRefreshInterval=3e4;bindingStore=null;aibotSessionId="";cwd;lastUsage=null;currentModel=null;constructor(e,s){super(),this.config=e,this.callbacks=s;const t=e.options??{};if(this.aibotSessionId=String(t.aibotSessionId??"").trim(),this.bindingStore=t.bindingStore instanceof E?t.bindingStore:null,this.cwd=this.resolveCwd(),this.bindingStore&&this.aibotSessionId){const i=this.bindingStore.getDeepSeekThreadId(this.aibotSessionId);i&&(this.deepSeekSessionId=i)}}resolveCwd(){if(this.bindingStore&&this.aibotSessionId){const e=this.bindingStore.get(this.aibotSessionId);if(e?.cwd)return e.cwd}return process.cwd()}async start(){this.alive=!0,this.notifyBindingReady(),o.info("deepseek-adapter","Ready (exec mode)")}async stop(){this.stopped=!0,this.alive=!1,this.stopComposing(),this.clearIdleTimer(),this.killActiveProcess()}isAlive(){return this.alive}async createSession(e){const s=this.deepSeekSessionId??`ds-${Date.now()}`;return this.notifyBindingReady(),s}async resumeSession(e,s){}async destroySession(e){this.deepSeekSessionId=null,this.persistSessionId(void 0)}sendPrompt(e){const s=new k(e.adapterSessionId);return this.runMessage(e,s).catch(t=>{s.emitError(t instanceof Error?t:new Error(String(t)))}),s}async cancel(e){this.killActiveProcess()}setPermissionHandler(e){}async ping(e){return this.alive}getStatus(){return{alive:this.alive,busy:this.activeEventId!==null,sessions:this.deepSeekSessionId?1:0}}getActiveEventIds(){return this.activeEventId?[this.activeEventId]:[]}clearActiveEventForShutdown(){this.clearIdleTimer(),this.killActiveProcess(),this.activeEventId=null}getMcpConfig(){return null}getUsageSnapshot(){return this.lastUsage}getSupportedCommands(){return[{name:"status",description:"Show session and working directory status"}]}async execCommand(e,s,t){return e==="status"?{status:"ok",message:`Session: ${this.deepSeekSessionId??"none"}, CWD: ${this.cwd}`,data:{sessionId:this.deepSeekSessionId,cwd:this.cwd,alive:this.alive}}:{status:"unsupported",message:`Unknown command: ${e}`}}async handleLocalAction(e){const s=e.action_type??"",t=e.params??{};switch(s){case"get_context":return this.callbacks.sendLocalActionResult(e.action_id,"ok",{sessionId:this.deepSeekSessionId,cwd:this.cwd,model:this.currentModel}),{handled:!0,kind:"get_context"};case"set_model":{const i=String(t.model_id??"").trim();return i?(this.currentModel=i,this.callbacks.sendLocalActionResult(e.action_id,"ok",{outcome:"model_set",modelId:i}),o.info("deepseek-adapter",`Model set to: ${i}`),{handled:!0,kind:"set_model"}):(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"invalid_params","model_id is required"),{handled:!0,kind:"set_model"})}default:return{handled:!1,kind:""}}}deliverInboundEvent(e){const s=_(e.content,{messageId:e.msg_id,quotedMessageId:e.quoted_message_id});if(this.activeEventId){o.info("deepseek-adapter",`Event ${e.event_id}: rejected, busy with ${this.activeEventId}`),this.callbacks.sendEventResult(e.event_id,"failed","agent busy");return}this.startNewMessage(e,s)}deliverStopEvent(e,s){this.activeEventId===e&&(this.callbacks.sendEventResult(e,"canceled","stopped by user"),this.clearActive())}startNewMessage(e,s){this.activeEventId=e.event_id,this.activeSessionId=e.session_id,this.chunkSeq=0,this.activeClientMsgId=`ds-${Date.now()}-${Math.random().toString(36).slice(2,8)}`,this.startComposing();const t={adapterSessionId:this.deepSeekSessionId??"",text:s,contextMessages:e.context_messages_json?JSON.parse(e.context_messages_json).map(n=>({senderId:n.sender_id??"unknown",content:n.content})):void 0},i=new k(this.deepSeekSessionId??"");this.runMessage(t,i,e.event_id,e.session_id).catch(n=>{o.error("deepseek-adapter",`Message failed: ${n}`),this.callbacks.sendEventResult(e.event_id,"failed",n instanceof Error?n.message:String(n)),this.clearActive()}),this.resetIdleTimer(e.event_id)}buildExecArgs(e){const s=["exec","--output-format","stream-json"];return this.currentModel&&s.push("--model",this.currentModel),this.deepSeekSessionId&&s.push("--resume",this.deepSeekSessionId),s.push("--",e),s}async runMessage(e,s,t,i){let n=e.text;e.contextMessages&&e.contextMessages.length>0&&(n=`Conversation context:
2
- ${e.contextMessages.map(c=>`[${c.senderId??"unknown"}]: ${c.content}`).join(`
3
- `)}
4
-
5
- Latest user message:
6
- ${n}`);const h=this.config.command||"codewhale",d=this.buildExecArgs(n),u={...process.env,...this.config.env},g=f(h,typeof u.PATH=="string"?u.PATH:void 0);o.info("deepseek-adapter",`Spawning: ${g} ${d.slice(0,5).join(" ")}...`);const r=T(g,d,{cwd:this.cwd,env:u}).process;return this.activeProcess=r,r.stderr?.on("data",c=>{const l=c.toString().trim();l&&o.info("deepseek-adapter",`[deepseek stderr] ${l}`)}),new Promise((c,l)=>{let p=!1,m="";const v=()=>{this.activeProcess=null};r.on("error",a=>{p||(p=!0,v(),l(a))}),r.on("exit",a=>{if(m.trim()&&this.handleOutputLine(m.trim(),t),m="",p){v();return}if(p=!0,v(),a!==0&&t&&this.activeEventId===t){l(new Error(`deepseek exec exited with code ${a}`));return}s.emitDone({status:"completed"}),c()}),w({input:r.stdout}).on("line",a=>{a.trim()&&this.handleOutputLine(a.trim(),t)}),r.stdin?.end()})}handleOutputLine(e,s){let t;try{t=JSON.parse(e)}catch{o.error("deepseek-adapter",`Invalid JSON: ${e.slice(0,200)}`);return}switch(t.type){case"content":{const i=t.content;i&&s&&this.activeEventId===s&&this.activeSessionId&&(this.chunkSeq++,this.callbacks.sendStreamChunk(s,this.activeSessionId,i,this.chunkSeq,!1,this.activeClientMsgId??void 0),this.startComposing(),this.resetIdleTimer(s));break}case"session_capture":{const i=t.content;i&&(this.deepSeekSessionId=i,this.persistSessionId(i),o.info("deepseek-adapter",`Session captured: ${i}`));break}case"metadata":{const i=t.meta;if(i){o.info("deepseek-adapter",`Metadata: model=${i.model}, tokens_in=${i.input_tokens}, tokens_out=${i.output_tokens}`);const n=Number(i.input_tokens??0),h=Number(i.output_tokens??0);if(n>0||h>0){const d=this.lastUsage;this.lastUsage={sampledAt:new Date().toISOString(),turns:(d?.turns??0)+1,total:{input:(d?.total.input??0)+n,output:(d?.total.output??0)+h}}}}break}case"tool_use":{const i=t.name,n=typeof t.input=="string"?t.input:JSON.stringify(t.input??{});i&&s&&this.activeEventId===s&&this.activeSessionId&&(o.info("deepseek-adapter",`Tool use: ${i}`),this.callbacks.sendToolUse(s,this.activeSessionId,i,n),this.resetIdleTimer(s));break}case"tool_result":{const i=t.name,n=t.output;s&&this.activeEventId===s&&this.activeSessionId&&(this.callbacks.sendToolResult(s,this.activeSessionId,i??"unknown",n??""),this.resetIdleTimer(s));break}case"done":{this.handleMessageCompleted(s);break}default:break}}handleMessageCompleted(e){if(this.stopComposing(),e&&this.activeEventId===e){const s=this.activeSessionId??"",t=this.activeClientMsgId??void 0;s&&(this.chunkSeq++,this.callbacks.sendStreamChunk(e,s,"",this.chunkSeq,!0,t)),this.callbacks.sendEventResult(e,"responded"),this.clearActive()}}killActiveProcess(){const e=this.activeProcess;if(this.activeProcess=null,e?.pid)try{e.kill("SIGTERM")}catch{}}notifyBindingReady(){!this.aibotSessionId||!this.cwd||this.callbacks.sendUpdateBindingCard(this.aibotSessionId,"ready",this.cwd)}persistSessionId(e){!this.bindingStore||!this.aibotSessionId||this.bindingStore.setDeepSeekThreadId(this.aibotSessionId,e)}startComposing(){if(!this.activeSessionId||this.composingTimer)return;this.stopComposing();const e=this.activeSessionId,s={ttl_ms:this.composingTTL};this.callbacks.sendSessionActivitySet(e,"composing",!0,s),this.composingTimer=setInterval(()=>{this.callbacks.sendSessionActivitySet(e,"composing",!0,s)},this.composingRefreshInterval),this.composingTTLClear=setTimeout(()=>{this.stopComposing()},this.composingTTL)}stopComposing(){this.composingTimer&&(clearInterval(this.composingTimer),this.composingTimer=null),this.composingTTLClear&&(clearTimeout(this.composingTTLClear),this.composingTTLClear=null),this.activeSessionId&&this.callbacks.sendSessionActivitySet(this.activeSessionId,"composing",!1)}resetIdleTimer(e){this.clearIdleTimer(),this.idleTimer=setTimeout(()=>{this.activeEventId===e&&(o.error("deepseek-adapter",`Agent idle for ${S/1e3}s: ${e}`),this.killActiveProcess(),this.callbacks.sendEventResult(e,"failed",`agent idle for ${S/1e3}s`),this.clearActive(),this.emit("stuck"))},S)}clearIdleTimer(){this.idleTimer&&(clearTimeout(this.idleTimer),this.idleTimer=null)}clearActive(){const e=this.activeEventId;this.stopComposing(),this.activeEventId=null,this.activeSessionId=null,this.chunkSeq=0,this.activeClientMsgId=null,this.clearIdleTimer(),e&&this.emit("eventDone",e)}}class k extends I{adapterSessionId;constructor(e){super(),this.adapterSessionId=e}emitDone(e){this.emit("done",e)}emitError(e){if(this.listenerCount("error")===0){o.warn("deepseek-adapter",`Prompt handle error (no listeners): ${e.message}`);return}this.emit("error",e)}async cancel(){}}export{y as DeepSeekAdapter};
@@ -1 +0,0 @@
1
- import{DeepSeekAdapter as e}from"./deepseek-adapter.js";export{e as DeepSeekAdapter};
@@ -1 +0,0 @@
1
- import{QwenAdapter as e}from"./qwen-adapter.js";export{e as QwenAdapter};
@@ -1,4 +0,0 @@
1
- import l from"node:path";import{fileURLToPath as m}from"node:url";import{EventEmitter as h}from"node:events";import{AgentProcess as f}from"../../agent/process.js";import{AcpClient as v,AcpAuthRequiredError as g}from"../../protocol/acp-client.js";import{AgentEventType as a}from"../../types/events.js";import{QuotedMessageStream as R}from"../../core/util/quoted-message-stream.js";import{InternalApiServer as A}from"../../core/mcp/internal-api-server.js";import{EventResultsStore as b}from"../../core/persistence/event-results-store.js";import{log as r}from"../../core/log/index.js";const d=l.dirname(m(import.meta.url)),w=300*1e3,u=60*1e3,I=200;class T extends h{type="qwen";config;callbacks;agentProcess=null;acpClient=null;internalApi=null;activeRun=null;pendingApprovals=new Map;bindingStore;eventResults=null;currentAibotSessionId;stopped=!1;cwd;model;promptTimeoutMs;clientMsgSeq=0;deferredEvents=new Map;sessionBindings=new Map;constructor(e,t,s,i){super(),this.config=e,this.callbacks=t,this.bindingStore=s,this.cwd=e.cwd??process.cwd(),this.model=e.options?.model,this.promptTimeoutMs=e.options?.promptTimeoutMs??w,i&&(this.eventResults=new b(i))}async start(){const e=[...this.config.args??[],"--acp"];this.model&&e.push("--model",this.model);const t={command:this.config.command||"qwen",args:e,cwd:this.cwd,env:this.config.env},s=await this.startInternalApiAndMcp();this.agentProcess=new f;const i=await this.agentProcess.start(t);r.info("qwen-adapter","Qwen process started"),this.agentProcess.on("exit",n=>{this.stopped||(r.error("qwen-adapter",`Process exited unexpectedly (code=${n})`),this.activeRun&&this.finishRun("failed",`qwen process exited (code=${n})`),this.emit("exit",n))}),this.acpClient=new v,this.acpClient.on("event",n=>this.handleAcpEvent(n));const o=this.currentAibotSessionId?this.bindingStore.getAcpSessionId(this.currentAibotSessionId):void 0;try{await this.acpClient.connect({transport:i,initialMode:"bypass",mcpServers:s,sessionId:o,cwd:this.cwd}),r.info("qwen-adapter",`ACP session ready: ${this.acpClient.sessionId}`),this.currentAibotSessionId&&this.bindingStore.setAcpSessionId(this.currentAibotSessionId,this.acpClient.sessionId);for(const[n,c]of this.sessionBindings)this.callbacks.sendUpdateBindingCard(n,"ready",c)}catch(n){if(n instanceof g){await this.handleAuthRequired(n);return}throw n}}async stop(){this.stopped=!0,this.deferredEvents.clear(),this.activeRun&&(this.activeRun.flushTimer&&(clearTimeout(this.activeRun.flushTimer),this.activeRun.flushTimer=null),this.activeRun.timeoutTimer&&(clearTimeout(this.activeRun.timeoutTimer),this.activeRun.timeoutTimer=null),this.activeRun.firstResponseTimer&&(clearTimeout(this.activeRun.firstResponseTimer),this.activeRun.firstResponseTimer=null),this.activeRun=null),this.acpClient&&(this.acpClient.removeAllListeners(),this.acpClient=null),this.agentProcess&&(await this.agentProcess.close(),this.agentProcess=null),this.internalApi&&(await this.internalApi.stop(),this.internalApi=null)}isAlive(){return this.acpClient?.isAlive??!1}async createSession(e){return this.acpClient?.sessionId??""}async resumeSession(e,t){}async destroySession(e){}sendPrompt(e){const t=new S(e.adapterSessionId);return this.acpClient?.isAlive&&this.acpClient.send(e.text).catch(s=>{t.emitError(s instanceof Error?s:new Error(String(s)))}),t}async cancel(e){this.activeRun&&this.acpClient&&(await this.acpClient.cancel(),this.flushStream(),this.finishRun("canceled","stopped by user"))}setPermissionHandler(e){}async ping(e){return this.acpClient?.ping(e)??!1}getStatus(){return{alive:this.acpClient?.isAlive??!1,busy:this.activeRun!==null,sessions:this.acpClient?1:0}}getMcpConfig(){if(!this.internalApi)return null;const e=l.resolve(d,"../../mcp/acp-mcp-server.js");return{name:"grix-connector-tools",command:process.execPath,args:[e,"--api-url",this.internalApi.url]}}get pendingApprovalEntries(){return this.pendingApprovals}get acpSessionOptions(){return this.acpClient?.sessionOptions??null}async handleLocalAction(e){const t=e.action_type??"",s=e.params??{};if(t==="exec_approve"||t==="exec_reject"){const i=String(s.tool_call_id??""),o=t==="exec_approve";if(!i)return this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"tool_call_id_required","tool_call_id is required"),{handled:!0,kind:"approval"};const n=this.pendingApprovals.get(i);return n?(this.pendingApprovals.delete(i),this.acpClient&&this.acpClient.respondPermission(n,{behavior:o?"allow":"deny"}).catch(c=>{r.error("qwen-adapter",`Failed to respond to permission: ${c}`)}),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: ${i}`),{handled:!0,kind:"approval"})}return{handled:!1,kind:""}}async startInternalApiAndMcp(){try{this.internalApi=new A,this.internalApi.setInvokeHandler(async(t,s)=>this.callbacks.agentInvoke(t,s)),await this.internalApi.start(0),r.info("qwen-adapter",`Internal API started at ${this.internalApi.url}`);const e=l.resolve(d,"../../mcp/acp-mcp-server.js");return[{name:"grix-connector-tools",command:process.execPath,args:[e,"--api-url",this.internalApi.url],env:{GRIX_CONNECTOR_INTERNAL_API:this.internalApi.url}}]}catch(e){r.error("qwen-adapter",`Failed to start MCP tools: ${e}`);return}}bindSession(e,t){return this.sessionBindings.get(e)?!1:(this.sessionBindings.set(e,t),this.bindingStore.set(e,t),this.acpClient?.sessionId&&this.bindingStore.setAcpSessionId(e,this.acpClient.sessionId),this.acpClient?.isAlive&&this.callbacks.sendUpdateBindingCard(e,"connected",t),!0)}getSessionCwd(e){return this.sessionBindings.get(e)}getSessionBindings(){return this.sessionBindings}deliverInboundEvent(e){if(this.callbacks.sendEventAck(e.event_id,e.session_id),this.eventResults?.has(e.session_id,e.event_id)){const t=this.eventResults.get(e.session_id,e.event_id);r.info("qwen-adapter",`Deduplicating event ${e.event_id} (cached: ${t.status})`),this.callbacks.sendEventResult(e.event_id,t.status,t.msg);return}if(this.activeRun){r.info("qwen-adapter",`Event ${e.event_id} rejected: busy`),this.callbacks.sendEventResult(e.event_id,"failed","agent busy");return}if(!this.acpClient?.isAlive){this.callbacks.sendEventResult(e.event_id,"failed","qwen agent not alive");return}if(e.session_id&&e.session_id!==this.currentAibotSessionId&&(this.currentAibotSessionId=e.session_id),!this.sessionBindings.has(e.session_id)){this.deferEvent(e),this.callbacks.sendStreamChunk(e.event_id,e.session_id,"Qwen needs a workspace before it can reply. Use /grix open <directory> to bind.",1,!1),this.callbacks.sendStreamChunk(e.event_id,e.session_id,"",2,!0),this.callbacks.sendEventResult(e.event_id,"responded");return}this.startRun(e,!1)}deliverStopEvent(e){this.activeRun?.eventId===e&&(this.flushStream(),this.finishRun("canceled","stopped by user"))}deferEvent(e){const t=this.deferredEvents.get(e.session_id)??[];t.some(s=>s.event.event_id===e.event_id)||(t.push({event:e,queuedAt:Date.now()}),this.deferredEvents.set(e.session_id,t),r.info("qwen-adapter",`Deferred event ${e.event_id} for session ${e.session_id} (queue: ${t.length})`))}replayDeferredEvents(e){const t=this.deferredEvents.get(e);if(!(!t||t.length===0)){this.deferredEvents.delete(e),r.info("qwen-adapter",`Replaying ${t.length} deferred events for session ${e}`);for(const{event:s}of t){if(this.activeRun){r.info("qwen-adapter",`Cannot replay ${s.event_id}: agent busy, dropping`);continue}this.startRun(s,!0)}}}startRun(e,t){this.activeRun={eventId:e.event_id,sessionId:e.session_id,threadId:e.thread_id,clientMsgId:`qwen_${++this.clientMsgSeq}_${Date.now()}`,chunkSeq:0,buffer:"",quotedStream:new R,responded:!1,silent:t,flushTimer:null,timeoutTimer:null,firstResponseTimer:null};const s=this.activeRun;s.firstResponseTimer=setTimeout(()=>{this.activeRun?.eventId===e.event_id&&!s.responded&&(r.error("qwen-adapter",`No response from agent within ${u}ms for ${e.event_id}`),this.finishRun("failed","agent not responding"))},u),s.timeoutTimer=setTimeout(()=>{this.activeRun?.eventId===e.event_id&&(r.error("qwen-adapter",`Prompt timed out for ${e.event_id}`),this.finishRun("failed","agent response timed out"))},this.promptTimeoutMs),this.callbacks.sendSessionComposing(e.session_id,!0),this.acpClient.send(e.content).catch(i=>{r.error("qwen-adapter",`Prompt failed: ${i}`),this.finishRun("failed",i instanceof Error?i.message:String(i))})}handleAcpEvent(e){if(e.type===a.PermissionRequest){this.handlePermissionRequest(e);return}const t=this.activeRun;if(t)switch(t.responded||(t.responded=!0,t.firstResponseTimer&&(clearTimeout(t.firstResponseTimer),t.firstResponseTimer=null)),e.type){case a.Text:{e.content&&this.appendToStream(t,e.content);break}case a.ToolUse:{e.toolName&&this.callbacks.sendToolUse(t.eventId,t.sessionId,e.toolName,e.toolInput??"");break}case a.ToolResult:{e.content&&this.callbacks.sendToolResult(t.eventId,t.sessionId,e.content);break}case a.Thinking:{e.content&&this.callbacks.sendThinking(t.eventId,t.sessionId,e.content);break}case a.Error:{r.error("qwen-adapter",`ACP error: ${e.error}`);break}case a.Result:{this.flushStream(),this.finishRun("responded");break}}}handlePermissionRequest(e){const t=e.permissionRequest;if(!t||!e.requestId||!this.acpClient)return;const s=t.toolCallId;this.pendingApprovals.set(s,e.requestId);const i=this.activeRun;i?this.callbacks.sendPermissionCard({eventId:i.eventId,sessionId:i.sessionId,toolCallId:s,toolName:t.toolName,toolTitle:t.toolTitle,options:t.options}):(r.info("qwen-adapter",`Permission request without active run, auto-approving: ${t.toolName}`),this.acpClient.respondPermission(e.requestId,{behavior:"allow"}),this.pendingApprovals.delete(s))}async handleAuthRequired(e){r.info("qwen-adapter",`Auth required, methods: ${e.authMethods.map(o=>o.id).join(", ")}`);const t=e.authMethods.find(o=>/oauth|browser/i.test(o.id))??e.authMethods[0];if(!t)throw e;const s=this.currentAibotSessionId??"";this.callbacks.sendAuthNotification(s,`Qwen authentication required (${t.id}). Initiating auth flow...`);for(const[o,n]of this.sessionBindings)this.callbacks.sendUpdateBindingCard(o,"failed",n);const i=await this.captureAuthUrl();i&&this.callbacks.sendAuthNotification(s,`Please open this URL to authenticate:
2
- ${i}
3
-
4
- Waiting for authentication to complete...`);try{await this.acpClient.authenticate(t.id),r.info("qwen-adapter","Authentication successful"),this.callbacks.sendAuthNotification(s,"Authentication successful. Resuming..."),await this.acpClient.connect({transport:this.agentProcess.transport,initialMode:"bypass",mcpServers:this.internalApi?[{name:"grix-connector-tools",command:process.execPath,args:[l.resolve(d,"../../mcp/acp-mcp-server.js"),"--api-url",this.internalApi.url],env:{GRIX_CONNECTOR_INTERNAL_API:this.internalApi.url}}]:void 0}),r.info("qwen-adapter",`ACP session ready after auth: ${this.acpClient.sessionId}`);for(const[o,n]of this.sessionBindings)this.callbacks.sendUpdateBindingCard(o,"ready",n)}catch(o){throw r.error("qwen-adapter",`Auth retry failed: ${o}`),o}}captureAuthUrl(){return new Promise(e=>{if(!this.agentProcess){e(null);return}const t=/https?:\/\/[^\s"')\]]+/;let s=!1;const i=o=>{if(s)return;const n=o.toString().replace(/\x1b\[[0-9;]*m/g,"").match(t);n&&(s=!0,this.agentProcess.removeListener("stderr",i),e(n[0]))};this.agentProcess.on("stderr",i),setTimeout(()=>{s||(s=!0,this.agentProcess.removeListener("stderr",i),e(null))},3e4)})}appendToStream(e,t){const s=e.quotedStream.consume(t);s.deltaContent&&(e.buffer+=s.deltaContent,e.flushTimer||(e.flushTimer=setTimeout(()=>this.flushStream(),I)))}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.callbacks.sendStreamChunk(e.eventId,e.sessionId,t,++e.chunkSeq,!1)}finishRun(e,t){const s=this.activeRun;if(!s)return;this.activeRun=null,this.callbacks.sendSessionComposing(s.sessionId,!1),s.flushTimer&&(clearTimeout(s.flushTimer),s.flushTimer=null),s.timeoutTimer&&(clearTimeout(s.timeoutTimer),s.timeoutTimer=null),s.firstResponseTimer&&(clearTimeout(s.firstResponseTimer),s.firstResponseTimer=null);const i=s.quotedStream.flush();i.deltaContent&&(s.buffer+=i.deltaContent),s.buffer&&(this.callbacks.sendStreamChunk(s.eventId,s.sessionId,s.buffer,++s.chunkSeq,!1),s.buffer=""),t&&this.callbacks.sendRunError(s.eventId,s.sessionId,t),this.callbacks.sendStreamChunk(s.eventId,s.sessionId,"",++s.chunkSeq,!0),s.silent||this.callbacks.sendEventResult(s.eventId,e,t),this.eventResults&&!s.silent&&this.eventResults.set({sessionId:s.sessionId,eventId:s.eventId,status:e,msg:t,updatedAt:Date.now()})}}class S extends h{adapterSessionId;constructor(e){super(),this.adapterSessionId=e}emitDone(e){this.emit("done",e)}emitError(e){this.emit("error",e)}async cancel(){}}export{T as QwenAdapter};
@@ -1 +0,0 @@
1
- import{EventEmitter as o}from"node:events";import c from"ws";const r="aibot-agent-api-v1",h=1;class l extends o{ws=null;seq=0;heartbeatTimer=null;heartbeatSec=30;connected=!1;config;constructor(e){super(),this.config={url:e.url,agentId:e.agentId,apiKey:e.apiKey,clientType:e.clientType,capabilities:e.capabilities??["stream_chunk","local_action_v1"],localActions:e.localActions??["exec_approve","exec_reject"]}}get isConnected(){return this.connected}async connect(){return new Promise((e,a)=>{const s=new c(this.config.url);this.ws=s;const i=setTimeout(()=>{a(new Error("Auth timeout: no auth_ack received within 15s")),s.close()},15e3);s.on("open",()=>{this.sendPacket("auth",{agent_id:this.config.agentId,api_key:this.config.apiKey,client_type:this.config.clientType,protocol_version:r,contract_version:h,capabilities:this.config.capabilities,local_actions:this.config.localActions})}),s.on("message",t=>{let n;try{n=JSON.parse(t.toString())}catch{return}this.handlePacket(n,i,e,a)}),s.on("close",(t,n)=>{this.connected=!1,this.stopHeartbeat(),clearTimeout(i),this.emit("close",t,n.toString())}),s.on("error",t=>{clearTimeout(i),this.emit("error",t),this.connected||a(t)})})}handlePacket(e,a,s,i){switch(e.cmd){case"auth_ack":{clearTimeout(a);const t=e.payload;t.code===0?(this.connected=!0,t.heartbeat_sec&&(this.heartbeatSec=t.heartbeat_sec),this.startHeartbeat(),this.emit("auth",t),s(t)):i(new Error(`Auth failed: code=${t.code} msg=${t.msg}`));break}case"ping":{this.sendPacket("pong",e.payload??{});break}case"event_msg":{this.emit("event",e.payload);break}case"local_action":{this.emit("localAction",e.payload);break}case"event_stop":{this.emit("stop",e.payload);break}case"kicked":{this.emit("kicked",e.payload),this.disconnect();break}case"error":{const t=e.payload;this.emit("error",new Error(`Server error: code=${t.code} msg=${t.msg}`));break}case"send_ack":case"send_nack":case"local_action_ack":break;default:break}}sendEventAck(e){this.sendPacket("event_ack",e)}sendStreamChunk(e){this.sendPacket("client_stream_chunk",e)}sendMsg(e){this.sendPacket("send_msg",e)}sendEventResult(e){this.sendPacket("event_result",e)}sendLocalActionResult(e){this.sendPacket("local_action_result",e)}sendEventStopAck(e){this.sendPacket("event_stop_ack",e)}sendEventStopResult(e){this.sendPacket("event_stop_result",e)}sendPing(){this.sendPacket("ping",{})}disconnect(){this.connected=!1,this.stopHeartbeat(),this.ws&&(this.ws.close(),this.ws=null)}sendPacket(e,a){if(!this.ws||this.ws.readyState!==c.OPEN)return;const s={cmd:e,seq:++this.seq,payload:a};this.ws.send(JSON.stringify(s))}startHeartbeat(){this.stopHeartbeat(),this.heartbeatTimer=setInterval(()=>{this.connected&&this.sendPing()},this.heartbeatSec*1e3)}stopHeartbeat(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null)}}export{l as AibotClient};
@@ -1 +0,0 @@
1
- export*from"./types.js";import{AibotClient as o}from"./client.js";export{o as AibotClient};
File without changes
@@ -1 +0,0 @@
1
- import{readAllowlist as r,writeAllowlist as s}from"./allowlist-store.js";import{log as a}from"../log/index.js";class h{filePath;writeQueue=Promise.resolve();constructor(e){this.filePath=e}async checkAccess(e){if(!e?.trim())return!1;const t=r(this.filePath);return t.includes(e)?!0:t.length===0?this.autoAdd(e):!1}async addOwner(e){e?.trim()&&(this.writeQueue=this.writeQueue.then(async()=>{const t=r(this.filePath);t.includes(e)||(t.push(e),await s(this.filePath,t))}).catch(t=>{a.error("allowlist-gate",`addOwner failed for ${e}: ${t instanceof Error?t.message:t}`)}),await this.writeQueue)}async removeOwner(e){e?.trim()&&(this.writeQueue=this.writeQueue.then(async()=>{const t=r(this.filePath),i=t.filter(l=>l!==e);i.length!==t.length&&await s(this.filePath,i)}).catch(t=>{a.error("allowlist-gate",`removeOwner failed for ${e}: ${t instanceof Error?t.message:t}`)}),await this.writeQueue)}async listOwners(){return r(this.filePath)}autoAdd(e){return new Promise(t=>{this.writeQueue=this.writeQueue.then(async()=>{const i=r(this.filePath);if(i.includes(e)){t(!0);return}if(i.length>0){t(!1);return}await s(this.filePath,[e]),t(!0)}).catch(i=>{a.error("allowlist-gate",`autoAdd failed for ${e}: ${i instanceof Error?i.message:i}`),t(!1)})})}}export{h as AllowlistGate};
@@ -1 +0,0 @@
1
- import{readJSONFile as o,writeJSONFileAtomic as i}from"../util/json-file.js";function n(t){const r=o(t);return r&&typeof r=="object"&&"owners"in r&&Array.isArray(r.owners)?r.owners.filter(e=>typeof e=="string"&&e.trim().length>0):[]}async function s(t,r){await i(t,{owners:r})}export{n as readAllowlist,s as writeAllowlist};
@@ -1 +0,0 @@
1
- import{AllowlistGate as l}from"./allowlist-gate.js";import{readAllowlist as t,writeAllowlist as s}from"./allowlist-store.js";export{l as AllowlistGate,t as readAllowlist,s as writeAllowlist};
@@ -1 +0,0 @@
1
- import{mkdir as m}from"node:fs/promises";import{join as c,basename as l}from"node:path";import{homedir as _}from"node:os";import{listFiles as u}from"./list-files.js";function d(){return _()}async function f(a,s){const o=a.params??{},t=o.parent_id?.trim(),i=o.show_hidden??!1;let e;t?e=t:e=s.resolveCwd()||d();try{return{status:"ok",result:{files:await u(e,i),current_path:e}}}catch(r){return r?.code==="ENOENT"?{status:"failed",error_code:"path_not_found",error_msg:`Directory not found: ${e}`}:r?.code==="ENOTDIR"?{status:"failed",error_code:"not_a_directory",error_msg:`Not a directory: ${e}`}:{status:"failed",error_code:"list_failed",error_msg:String(r.message||r)}}}async function p(a,s){const o=a.params??{},t=o.name?.trim(),i=o.parent_id?.trim();if(!t)return{status:"failed",error_code:"name_required",error_msg:"Folder name is required"};if(/[/\\]/.test(t))return{status:"failed",error_code:"invalid_name",error_msg:"Folder name must not contain path separators"};const e=i||s.resolveCwd()||d(),r=c(e,t);try{return await m(r),{status:"ok",result:{id:r,name:l(r),is_directory:!0}}}catch(n){return n?.code==="EEXIST"?{status:"failed",error_code:"already_exists",error_msg:`Folder already exists: ${r}`}:{status:"failed",error_code:"create_failed",error_msg:String(n.message||n)}}}export{p as handleCreateFolderAction,f as handleFileListAction};
@@ -1 +0,0 @@
1
- import{readdir as r,stat as m}from"node:fs/promises";import{join as l,extname as d}from"node:path";const x={pdf:"application/pdf",doc:"application/msword",docx:"application/vnd.openxmlformats-officedocument.wordprocessingml.document",xls:"application/vnd.ms-excel",xlsx:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",ppt:"application/vnd.ms-powerpoint",pptx:"application/vnd.openxmlformats-officedocument.presentationml.presentation",txt:"text/plain",md:"text/markdown",csv:"text/csv",json:"application/json",xml:"application/xml",yaml:"text/yaml",yml:"text/yaml",html:"text/html",css:"text/css",js:"text/javascript",ts:"text/typescript",zip:"application/zip",rar:"application/x-rar-compressed","7z":"application/x-7z-compressed",tar:"application/x-tar",gz:"application/gzip",jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",gif:"image/gif",webp:"image/webp",svg:"image/svg+xml",mp4:"video/mp4",mov:"video/quicktime",avi:"video/x-msvideo",mkv:"video/x-matroska",webm:"video/webm",mp3:"audio/mpeg",wav:"audio/wav",flac:"audio/flac",aac:"audio/aac"};function n(a){const p=d(a).slice(1).toLowerCase();return x[p]}async function f(a,p=!1){const c=await r(a,{withFileTypes:!0}),s=[];for(const i of c){if(!p&&i.name.startsWith("."))continue;const t=l(a,i.name),e={id:t,name:i.name,is_directory:i.isDirectory()};try{if(i.isDirectory()){const o=await m(t);e.modified_at=o.mtime.toISOString()}else{const o=await m(t);e.size=o.size,e.modified_at=o.mtime.toISOString(),e.mime_type=n(i.name)}}catch{}s.push(e)}return s.sort((i,t)=>i.is_directory!==t.is_directory?i.is_directory?-1:1:i.name.localeCompare(t.name)),s}export{f as listFiles,n as resolveMimeType};
File without changes
package/dist/log.js DELETED
@@ -1,3 +0,0 @@
1
- import{createWriteStream as g,mkdirSync as l,existsSync as f}from"node:fs";import{join as i}from"node:path";import{homedir as m}from"node:os";const n=i(m(),".grix"),s={base:n,config:i(n,"config"),log:i(n,"log"),data:i(n,"data")};function S(){for(const o of Object.values(s))f(o)||l(o,{recursive:!0})}let a=null;function $(){const o=new Date().toISOString().slice(0,10),r=i(s.log,`grix-acp-${o}.log`);a=g(r,{flags:"a"})}function c(){return new Date().toISOString().slice(11,19)}const u={info(o,r,...t){const e=`${c()} [${o}] ${r}${t.length?" "+t.map(String).join(" "):""}`;console.log(e),a?.write(e+`
2
- `)},error(o,r,...t){const e=`${c()} [${o}] ERROR ${r}${t.length?" "+t.map(String).join(" "):""}`;console.error(e),a?.write(e+`
3
- `)}};export{s as GRIX_PATHS,S as ensureGrixDirs,$ as initLogger,u as log};
package/dist/main.js DELETED
@@ -1,31 +0,0 @@
1
- #!/usr/bin/env node
2
- import x from"node:path";import{Manager as E}from"./manager.js";import{ensureGrixDirs as k,initLogger as O,log as a}from"./core/log/index.js";import{HealthServer as I}from"./core/runtime/index.js";import{writePidFile as R,removePidFile as l}from"./core/runtime/index.js";import{resolveRuntimePaths as g}from"./core/config/index.js";import{ServiceManager as T}from"./service/service-manager.js";import{acquireDaemonLock as $,releaseDaemonLock as p}from"./runtime/daemon-lock.js";import{writeDaemonStatus as f,removeDaemonStatus as F}from"./runtime/service-state.js";const n=process.argv.slice(2),m=[],s={};for(let e=0;e<n.length;e++)n[e].startsWith("--")&&n[e+1]&&!n[e+1].startsWith("--")?(s[n[e].slice(2)]=n[e+1],e++):n[e].startsWith("--")?s[n[e].slice(2)]="true":m.push(n[e]);if(s.help&&(console.log(`grix-connector \u2014 Unified AI Agent Bridge
3
-
4
- Usage: grix-connector [options]
5
- grix-connector service <action> [options]
6
-
7
- Actions (service):
8
- install Install and start as OS service
9
- start Start the OS service
10
- stop Stop the OS service
11
- restart Restart the OS service
12
- uninstall Uninstall the OS service
13
- status Show service and daemon status
14
-
15
- Options:
16
- --config-dir <path> Config directory (default: ~/.grix/config)
17
- --profile <name> Profile name for config subdirectory
18
- --health-port <port> Health check port (default: 19579)
19
- --help Show this help message
20
-
21
- Platform services:
22
- macOS: launchd (LaunchAgent)
23
- Linux: systemd --user
24
- Windows: Task Scheduler
25
-
26
- Examples:
27
- grix-connector # Run in foreground
28
- grix-connector service install # Install as OS service
29
- grix-connector service status # Check service status
30
- grix-connector service uninstall # Remove OS service
31
- `),process.exit(0)),m[0]==="service"){const e=m[1],t=["install","start","stop","restart","uninstall","status"];(!e||!t.includes(e))&&(console.error(`Usage: grix-connector service <${t.join("|")}>`),process.exit(1));const o=g(),w=s["config-dir"]??(s.profile?`${o.configDir}/${s.profile}`:void 0),D=x.resolve(process.argv[1]||`${o.rootDir}/dist/main.js`),c=new T({cliPath:D,nodePath:process.execPath});try{let r;switch(e){case"install":r=await c.install({rootDir:o.rootDir,configDir:w});break;case"start":r=await c.start({rootDir:o.rootDir});break;case"stop":r=await c.stop({rootDir:o.rootDir});break;case"restart":r=await c.restart({rootDir:o.rootDir});break;case"uninstall":r=await c.uninstall({rootDir:o.rootDir});break;case"status":r=await c.status({rootDir:o.rootDir});break}console.log(JSON.stringify(r,null,2)),process.exit(0)}catch(r){console.error(`service ${e} failed: ${r instanceof Error?r.message:r}`),process.exit(1)}}const i=g(),N=s["config-dir"]??(s.profile?`${i.configDir}/${s.profile}`:void 0),h=new E,d=new I;let S=!1;async function u(e){if(S)return;S=!0,a.info("main",`Received ${e}, shutting down...`),d.markShuttingDown();const t=setTimeout(()=>{a.error("main","Shutdown timed out, forcing exit"),p(i.daemonLockFile).catch(()=>{}),l(),process.exit(2)},1e4);try{await h.stop(),await d.stop(),await p(i.daemonLockFile),await F(i.daemonStatusFile).catch(()=>{}),clearTimeout(t),l(),a.info("main","Shutdown complete"),process.exit(0)}catch(o){a.error("main",`Shutdown error: ${o}`),p(i.daemonLockFile).catch(()=>{}),l(),process.exit(2)}}async function P(){k(),O();try{await $(i.daemonLockFile,i.rootDir)}catch(t){console.error(t instanceof Error?t.message:t),process.exit(1)}R(),a.info("main",`grix-connector starting (PID ${process.pid})`),await f(i.daemonStatusFile,{state:"starting",pid:process.pid,updated_at:Date.now()});const e=parseInt(s["health-port"]??process.env.GRIX_HEALTH_PORT??"19579",10);await d.start(e),process.on("SIGINT",()=>u("SIGINT")),process.on("SIGTERM",()=>u("SIGTERM")),process.on("uncaughtException",t=>{a.error("main",`Uncaught exception: ${t instanceof Error?t.stack:t}`),!v(t)&&u("uncaughtException")}),process.on("unhandledRejection",t=>{a.error("main",`Unhandled rejection: ${t}`),!v(t)&&u("unhandledRejection")}),d.setStatusProvider(()=>h.getAgentsStatus()),await h.start(N),await f(i.daemonStatusFile,{state:"running",pid:process.pid,updated_at:Date.now()}),process.send&&process.send("ready"),a.info("main","grix-connector ready")}P().catch(e=>{a.error("main",`Fatal: ${e}`),p(i.daemonLockFile).catch(()=>{}),l(),process.exit(1)});const A=new Set(["ECONNRESET","ECONNREFUSED","ETIMEDOUT","EPIPE","EAI_AGAIN","ENOTFOUND","EHOSTUNREACH","ENETUNREACH"]);function v(e){return e instanceof Error&&"code"in e?A.has(e.code):!1}
@@ -1 +0,0 @@
1
- import*as n from"node:net";const i={bind:"127.0.0.1",port:0,endpoint:"/mcp",sessionTimeoutMs:18e5,invokeTimeoutMs:3e4};function s(u){const e={bind:u?.bind??i.bind,port:u?.port??i.port,endpoint:u?.endpoint??i.endpoint,sessionTimeoutMs:u?.sessionTimeoutMs??i.sessionTimeoutMs,invokeTimeoutMs:u?.invokeTimeoutMs??i.invokeTimeoutMs,allowedOrigins:u?.allowedOrigins,allowedHosts:u?.allowedHosts};return t(e.bind),e.port!==0&&o(e.port),r(e.sessionTimeoutMs),e}function t(u){if(!u||!n.isIPv4(u)&&!n.isIPv6(u))throw new Error(`\u914D\u7F6E\u6821\u9A8C\u5931\u8D25: bind \u5730\u5740 "${u}" \u4E0D\u662F\u5408\u6CD5\u7684 IPv4 \u6216 IPv6 \u5730\u5740`)}function o(u){if(!Number.isInteger(u)||u<1||u>65535)throw new Error(`\u914D\u7F6E\u6821\u9A8C\u5931\u8D25: port \u503C ${u} \u4E0D\u5728\u5408\u6CD5\u8303\u56F4 1-65535 \u5185\u6216\u4E0D\u662F\u6574\u6570`)}function r(u){if(!Number.isInteger(u)||u<1e3||u>864e5)throw new Error(`\u914D\u7F6E\u6821\u9A8C\u5931\u8D25: session_timeout_ms \u503C ${u} \u4E0D\u5728\u5408\u6CD5\u8303\u56F4 1000-86400000 \u5185`)}export{s as createDefaultGatewayConfig};
@@ -1 +0,0 @@
1
- const a=3e4;class c{connectionManager;onDisconnected;bindings=new Map;constructor(n,i){this.connectionManager=n,this.onDisconnected=i}async bind(n,i){if(this.bindings.has(n))throw new Error(`Session ${n} is already bound to a connection`);const e=await this.connectWithTimeout(i),t=[],s=e.onDisconnected(()=>{this.removeBinding(n),this.onDisconnected(n)});return t.push(s),this.bindings.set(n,{sessionId:n,handle:e,subscriptions:t}),e}getHandle(n){return this.bindings.get(n)?.handle}unbind(n){const i=this.bindings.get(n);if(i){this.bindings.delete(n);for(const e of i.subscriptions)e();i.handle.disconnect()}}unbindAll(){const n=[...this.bindings.keys()];for(const i of n)this.unbind(i)}connectWithTimeout(n){return new Promise((i,e)=>{let t=!1;const s=setTimeout(()=>{t||(t=!0,e(new Error("Connection bind timeout after 30000ms")))},3e4);this.connectionManager.connect({agentId:n.agentId,apiKey:n.apiKey,url:n.wsUrl,clientType:n.clientType,capabilities:["agent_invoke"],adapterHint:`${n.clientType}/base`},{maxRetries:0}).then(o=>{t?o.disconnect():(t=!0,clearTimeout(s),i(o))}).catch(o=>{t||(t=!0,clearTimeout(s),e(o))})})}removeBinding(n){const i=this.bindings.get(n);if(i){this.bindings.delete(n);for(const e of i.subscriptions)e()}}}export{c as ConnectionBindingImpl};
@@ -1 +0,0 @@
1
- import{splitTextForAibotProtocol as v}from"../../core/protocol/protocol-text.js";const d=new Set(["grix_reply","grix_complete","grix_event_ack","grix_composing","grix_access_control","grix_status"]);function g(n){return d.has(n)}function p(n,e,t){switch(e){case"grix_reply":return f(n,t);case"grix_complete":return x(n,t);case"grix_event_ack":return C(n,t);case"grix_composing":return S(n,t);case"grix_access_control":return m(n,t);case"grix_status":return y(n);default:return r(`\u672A\u77E5\u4E8B\u4EF6\u5DE5\u5177: ${e}`)}}function f(n,e){const t=String(e.event_id??""),s=String(e.session_id??""),i=String(e.text??""),a=e.quoted_message_id,l=e.is_final===!0;if(!s)return r("\u7F3A\u5C11\u5FC5\u586B\u53C2\u6570: session_id");if(!i)return r("\u7F3A\u5C11\u5FC5\u586B\u53C2\u6570: text");const _=v(i),c=`reply_${t||"proactive"}_${Date.now()}`;for(let o=0;o<_.length;o++)n.sendStreamChunk({event_id:t||void 0,session_id:s,delta_content:_[o],chunk_seq:o+1,is_finish:!1,client_msg_id:c,quoted_message_id:o===0?a:void 0});return n.sendStreamChunk({event_id:t||void 0,session_id:s,delta_content:"",chunk_seq:_.length+1,is_finish:!0,client_msg_id:c}),l&&t&&n.sendEventResult({event_id:t,status:"responded"}),u({ok:!0,chunks:_.length,client_msg_id:c})}function x(n,e){const t=String(e.event_id??""),s=String(e.status??""),i=e.msg;return t?["responded","canceled","failed"].includes(s)?(n.sendEventResult({event_id:t,status:s,msg:i}),u({ok:!0,event_id:t,status:s})):r("status \u5FC5\u987B\u4E3A responded\u3001canceled \u6216 failed"):r("\u7F3A\u5C11\u5FC5\u586B\u53C2\u6570: event_id")}function C(n,e){const t=String(e.event_id??""),s=e.session_id;return t?(n.sendEventAck({event_id:t,session_id:s,received_at:Date.now()}),u({ok:!0,event_id:t})):r("\u7F3A\u5C11\u5FC5\u586B\u53C2\u6570: event_id")}function S(n,e){const t=String(e.session_id??""),s=e.active===!0,i=e.event_id;return t?(n.sendSessionActivitySet({session_id:t,kind:"composing",active:s,ref_event_id:i,ttl_ms:s?3e4:void 0}),u({ok:!0,session_id:t,active:s})):r("\u7F3A\u5C11\u5FC5\u586B\u53C2\u6570: session_id")}function m(n,e){const t=String(e.action??""),s={pair_approve:"pair_approve",pair_deny:"pair_deny",allow_sender:"sender_allow",remove_sender:"sender_remove",set_policy:"policy_set"}[t];if(!s)return r(`\u672A\u77E5 access_control action: ${t}`);const i={};return e.code!=null&&(i.code=e.code),e.sender_id!=null&&(i.sender_id=e.sender_id),e.policy!=null&&(i.policy=e.policy),{content:[{type:"text",text:"__ASYNC_ACCESS_CONTROL__"}],_async:{verb:s,payload:i}}}function y(n){const e=n.status,t=n.getStatusSnapshot();return u({connected:e==="ready",status:e,connected_at:t.connectedAt})}function u(n){return{content:[{type:"text",text:JSON.stringify(n)}],isError:!1}}function r(n){return{content:[{type:"text",text:n}],isError:!0}}export{d as EVENT_TOOL_NAMES,p as executeEventTool,g as isEventTool};
@@ -1 +0,0 @@
1
- import{createServer as N}from"node:http";import{Server as O}from"@modelcontextprotocol/sdk/server/index.js";import{StreamableHTTPServerTransport as j}from"@modelcontextprotocol/sdk/server/streamableHttp.js";import{ListToolsRequestSchema as R,CallToolRequestSchema as E}from"@modelcontextprotocol/sdk/types.js";import{createSecurityPolicy as h}from"./security.js";import{SessionManagerImpl as q}from"./session-manager.js";import{ToolRegistryImpl as A}from"./tool-registry.js";import{ToolExecutorImpl as C}from"./tool-executor.js";function J(n,r){let a=null,p=!1,m,c;const S=new A,v=new C;let s=null;return{async start(){m=h({serverPort:n.port,allowedOrigins:n.allowedOrigins??[],allowedHosts:n.allowedHosts??[]}),c=new q({maxSessions:1,sessionTimeoutMs:n.sessionTimeoutMs,onSessionExpired:e=>{w(e)}}),a=N((e,t)=>{g(e,t)}),await new Promise((e,t)=>{a.on("error",t),a.listen(n.port,n.bind,()=>{a.removeListener("error",t),p=!0;const i=a.address();i&&typeof i=="object"&&(n.port=i.port,m=h({serverPort:n.port,allowedOrigins:n.allowedOrigins??[],allowedHosts:n.allowedHosts??[]})),e()})})},async stop(){if(p=!1,a&&(await new Promise(e=>{a.close(()=>e())}),a=null),s){try{await s.transport.close()}catch{}try{await s.mcpServer.close()}catch{}s=null}c&&(await c.closeAll(),c.dispose())},getStatus(){return{listening:p,url:`http://${n.bind}:${n.port}${n.endpoint}`,activeSessions:s?1:0}},pushEvent(e){s&&y(s.mcpServer,"notifications/message",{event_id:e.event_id,session_id:e.session_id,sender_id:e.sender_id??"",content:e.content,msg_type:e.msg_type??1,msg_id:e.msg_id??"",session_type:e.session_type??1,quoted_message_id:e.quoted_message_id??"",attachments:e.attachments??[],context_messages:e.context_messages??[]},s.transport)},pushStop(e){s&&y(s.mcpServer,"notifications/event_stop",{event_id:e.event_id,stop_id:e.stop_id,session_id:e.session_id,reason:e.reason??""},s.transport)},pushRevoke(e){s&&y(s.mcpServer,"notifications/event_revoke",{event_id:e.event_id,session_id:e.session_id,reason:e.reason??""},s.transport)},pushLocalAction(e){s&&y(s.mcpServer,"notifications/local_action",e,s.transport)}};async function g(e,t){if(new URL(e.url??"/",`http://${e.headers.host??"localhost"}`).pathname!==n.endpoint){t.writeHead(404,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:"Not Found"}));return}const i=m.validateRequest(e);if(!i.ok){t.writeHead(i.statusCode??403,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:i.message}));return}const o=e.method?.toUpperCase();if(o==="GET"){const d=f(e);if(!d||!s||s.sessionId!==d){t.writeHead(400,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:"Bad Request: missing or invalid session"}));return}c.touchActivity(d),await s.transport.handleRequest(e,t);return}if(o==="DELETE"){await x(e,t);return}if(o==="POST"){await _(e,t);return}t.writeHead(405,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:"Method Not Allowed"}))}async function _(e,t){const i=await b(e);let o;try{o=JSON.parse(i)}catch{t.writeHead(400,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:"Invalid JSON body"}));return}const d=I(o),l=e.headers.accept??"";if(!l.includes("application/json")||!l.includes("text/event-stream")){t.writeHead(406,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:"Not Acceptable: Accept header must include both application/json and text/event-stream"}));return}if(d)await T(e,t,o);else{const u=f(e);if(!u){t.writeHead(400,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:"Bad Request: missing Mcp-Session-Id header"}));return}if(!s||s.sessionId!==u){t.writeHead(404,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:"Not Found: session does not exist or has expired"}));return}c.touchActivity(u),await s.transport.handleRequest(e,t,o)}}async function T(e,t,i){if(s){t.writeHead(503,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:"Service Unavailable: gateway already has an active session"}));return}const o=c.createSession().sessionId,d=new j({sessionIdGenerator:()=>o,enableJsonResponse:!1,onsessioninitialized:()=>{},onsessionclosed:()=>{w(o)}}),l=new O({name:"grix-mcp-server",version:"1.0.0"},{capabilities:{tools:{}}});H(l,o),await l.connect(d),s={transport:d,mcpServer:l,sessionId:o},await d.handleRequest(e,t,i);try{c.markReady(o)}catch{}}async function x(e,t){const i=f(e);if(!i){t.writeHead(400,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:"Bad Request: missing Mcp-Session-Id header"}));return}if(!s||s.sessionId!==i){t.writeHead(404,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:"Not Found: session does not exist or has expired"}));return}await s.transport.handleRequest(e,t)}function H(e,t){const i=S.getTools();e.setRequestHandler(R,async()=>({tools:i.map(o=>({name:o.name,description:o.description,inputSchema:o.inputSchema}))})),e.setRequestHandler(E,async o=>{const{name:d,arguments:l}=o.params,u=c.getSession(t);return!u||u.state!=="ready"?{content:[{type:"text",text:`Session \u72B6\u6001\u4E0D\u53EF\u7528: ${u?.state??"unknown"}`}],isError:!0}:r.status!=="ready"?{content:[{type:"text",text:`\u8FDE\u63A5\u4E0D\u53EF\u7528: \u5F53\u524D\u72B6\u6001\u4E3A ${r.status}`}],isError:!0}:(c.touchActivity(t),v.execute(r,d,l??{},n.invokeTimeoutMs))})}async function w(e){if(s?.sessionId===e){try{await s.mcpServer.close()}catch{}s=null}try{await c.closeSession(e)}catch{}}}function y(n,r,a,p){return p?p.send({jsonrpc:"2.0",method:r,params:a}):Promise.resolve()}function f(n){const r=n.headers["mcp-session-id"];return Array.isArray(r)?r[0]:r||void 0}function I(n){return n&&typeof n=="object"&&"method"in n?n.method==="initialize":Array.isArray(n)?n.some(r=>r&&typeof r=="object"&&r.method==="initialize"):!1}function b(n){return new Promise((r,a)=>{const p=[];n.on("data",m=>p.push(m)),n.on("end",()=>r(Buffer.concat(p).toString("utf-8"))),n.on("error",a)})}export{J as createMcpGateway};
@@ -1 +0,0 @@
1
- import{createMcpGateway as a}from"./gateway.js";import{createSecurityPolicy as e}from"./security.js";import{SessionManagerImpl as r}from"./session-manager.js";import{createDefaultGatewayConfig as t}from"./config.js";export{r as SessionManagerImpl,t as createDefaultGatewayConfig,a as createMcpGateway,e as createSecurityPolicy};
@@ -1 +0,0 @@
1
- function a(e){const t=new Set([`http://127.0.0.1:${e.serverPort}`,`http://localhost:${e.serverPort}`,...e.allowedOrigins]),o=new Set([`127.0.0.1:${e.serverPort}`,`localhost:${e.serverPort}`,...e.allowedHosts]);return{validateRequest(s){const r=i(s,t);if(!r.ok)return r;const n=l(s,o);return n.ok?{ok:!0}:n}}}function i(e,t){const o=e.headers.origin;return o?t.has(o)?{ok:!0}:{ok:!1,statusCode:403,message:`Origin not allowed: ${o}`}:{ok:!0}}function l(e,t){const o=e.headers.host;return o?t.has(o)?{ok:!0}:{ok:!1,statusCode:403,message:`Host not allowed: ${o}`}:{ok:!1,statusCode:403,message:"Missing Host header"}}export{a as createSecurityPolicy};
@@ -1 +0,0 @@
1
- import{randomUUID as n}from"node:crypto";const i={initializing:0,ready:1,closing:2,closed:3},o=6e4;class a{sessions=new Map;config;scanTimer=null;constructor(s){this.config=s,this.startScanTimer()}createSession(){if(this.getActiveCount()>=this.config.maxSessions)throw new Error(`Session limit exceeded: max ${this.config.maxSessions} active sessions`);const s=Date.now(),t={sessionId:n(),createdAt:s,lastActivityAt:s,state:"initializing"};return this.sessions.set(t.sessionId,t),t}getSession(s){return this.sessions.get(s)}markReady(s){const t=this.sessions.get(s);if(!t)throw new Error(`Session not found: ${s}`);this.transitionState(t,"ready")}async closeSession(s){const t=this.sessions.get(s);if(!t)throw new Error(`Session not found: ${s}`);t.state!=="closed"&&(t.state!=="closing"&&this.transitionState(t,"closing"),this.transitionState(t,"closed"))}touchActivity(s){const t=this.sessions.get(s);t&&(t.lastActivityAt=Date.now())}getActiveCount(){let s=0;for(const t of this.sessions.values())t.state!=="closed"&&s++;return s}async closeAll(){const s=[];for(const t of this.sessions.values())t.state!=="closed"&&s.push(this.closeSession(t.sessionId));await Promise.all(s)}dispose(){this.scanTimer!==null&&(clearInterval(this.scanTimer),this.scanTimer=null)}startScanTimer(){this.scanTimer=setInterval(()=>{this.scanExpiredSessions()},o)}scanExpiredSessions(){const s=Date.now();for(const t of this.sessions.values())t.state==="closed"||t.state==="closing"||s-t.lastActivityAt>this.config.sessionTimeoutMs&&this.expireSession(t.sessionId)}async expireSession(s){await this.closeSession(s),await this.config.onSessionExpired?.(s)}transitionState(s,t){const e=i[s.state];if(i[t]<=e)throw new Error(`Invalid state transition: cannot transition from '${s.state}' to '${t}'`);s.state=t}}export{a as SessionManagerImpl};
@@ -1 +0,0 @@
1
- import{toolCallToInvoke as i}from"../../core/mcp/tools.js";import{ToolRegistryImpl as l}from"./tool-registry.js";import{validateToolArgs as a}from"./tool-schemas.js";import{isEventTool as p,executeEventTool as d}from"./event-tool-executor.js";class y{registry;constructor(){this.registry=new l}async execute(r,e,t,n){if(!this.registry.hasTool(e))return this.errorResult(`\u672A\u77E5\u5DE5\u5177: ${e}`);const s=a(e,t);if(!s.valid)return this.errorResult(`\u53C2\u6570\u6821\u9A8C\u5931\u8D25: ${s.error}`);if(r.status!=="ready")return this.errorResult(`\u8FDE\u63A5\u4E0D\u53EF\u7528: \u5F53\u524D\u72B6\u6001\u4E3A ${r.status}`);if(p(e))return this.executeEventTool(r,e,t);const o=i(e,t);try{const u=await r.agentInvoke(o.action,o.params,n);return this.normalizeResult(u)}catch(u){const c=u instanceof Error?u.message:String(u);return c.toLowerCase().includes("timeout")?this.errorResult(`\u8C03\u7528\u8D85\u65F6: ${c}`):this.errorResult(`\u8C03\u7528\u5931\u8D25: ${c}`)}}normalizeResult(r){if(r==null||typeof r!="object")return this.successResult(r??null);const e=r,t=typeof e.code=="number"?e.code:0;if(t===0){const s="data"in e?e.data:null;return this.successResult(s??null)}const n=typeof e.msg=="string"?e.msg:"\u672A\u77E5\u9519\u8BEF";return this.errorResult(`\u4E0A\u6E38\u9519\u8BEF [code=${t}]: ${n}`)}successResult(r){return{content:[{type:"text",text:JSON.stringify(r)}],isError:!1}}errorResult(r){return{content:[{type:"text",text:r}],isError:!0}}async executeEventTool(r,e,t){return e==="grix_access_control"?this.executeAccessControl(r,t):d(r,e,t)}async executeAccessControl(r,e){const t=String(e.action??""),n={pair_approve:"pair_approve",pair_deny:"pair_deny",allow_sender:"sender_allow",remove_sender:"sender_remove",set_policy:"policy_set"}[t];if(!n)return this.errorResult(`\u672A\u77E5 access_control action: ${t}`);const s={};e.code!=null&&(s.code=e.code),e.sender_id!=null&&(s.sender_id=e.sender_id),e.policy!=null&&(s.policy=e.policy);try{const o=await r.agentInvoke("claude_access_control",{verb:n,payload:s},3e4);return this.successResult(o)}catch(o){const u=o instanceof Error?o.message:String(o);return this.errorResult(`access_control \u8C03\u7528\u5931\u8D25: ${u}`)}}}export{y as ToolExecutorImpl};
@@ -1 +0,0 @@
1
- import{TOOLS as o,EVENT_TOOLS as s}from"../../core/mcp/tools.js";const e=new Set(["grix_query","grix_group","grix_message_send","grix_message_unsend","grix_admin"]),r=new Set(["grix_reply","grix_complete","grix_event_ack","grix_composing","grix_access_control","grix_status"]);class a{tools;toolMap;constructor(){this.tools=[...o.filter(t=>e.has(t.name)),...s.filter(t=>r.has(t.name))],this.toolMap=new Map(this.tools.map(t=>[t.name,t]))}getTools(){return this.tools}getTool(t){return this.toolMap.get(t)}hasTool(t){return this.toolMap.has(t)}}export{a as ToolRegistryImpl};
@@ -1 +0,0 @@
1
- const o={required:["action"],properties:{action:{type:"string",enum:["contact_search","session_search","message_history","message_search"]},id:{type:"string"},keyword:{type:"string",maxLength:200},limit:{type:"integer",minimum:1,maximum:100},offset:{type:"integer",minimum:0},sessionId:{type:"string"},beforeId:{type:"string"}}},a={required:["action"],properties:{action:{type:"string",enum:["create","detail","leave","add_members","remove_members","update_member_role","update_all_members_muted","update_member_speaking","dissolve"]},sessionId:{type:"string"},name:{type:"string",maxLength:128},memberIds:{type:"array",items:{type:"string"},maxItems:100},memberTypes:{type:"array",items:{type:"integer",enum:[1,2]}},memberId:{type:"string"},role:{type:"integer",enum:[1,2]},memberType:{type:"integer"},allMembersMuted:{type:"boolean"},isSpeakMuted:{type:"boolean"},canSpeakWhenAllMuted:{type:"boolean"}}},p={required:["sessionId","content"],properties:{sessionId:{type:"string"},content:{type:"string",maxLength:1e4},msgType:{type:"integer"},quotedMessageId:{type:"string"},threadId:{type:"string"}}},m={required:["sessionId","msgId"],properties:{sessionId:{type:"string"},msgId:{type:"string"}}},g={required:["action"],properties:{action:{type:"string",enum:["create_agent","list_categories","create_category","update_category","assign_category","rotate_api_key"]},agentName:{type:"string"},introduction:{type:"string"},isMain:{type:"boolean"},agentId:{type:"string"},categoryId:{type:"string"},name:{type:"string"},parentId:{type:"string"},sortOrder:{type:"integer"}}},y={required:["session_id","text"],properties:{event_id:{type:"string"},session_id:{type:"string"},text:{type:"string",maxLength:5e4},quoted_message_id:{type:"string"},is_final:{type:"boolean"}}},d={required:["event_id","status"],properties:{event_id:{type:"string"},status:{type:"string",enum:["responded","canceled","failed"]},msg:{type:"string",maxLength:500}}},c={required:["event_id"],properties:{event_id:{type:"string"},session_id:{type:"string"}}},l={required:["session_id","active"],properties:{session_id:{type:"string"},active:{type:"boolean"},event_id:{type:"string"}}},_={required:["action"],properties:{action:{type:"string",enum:["pair_approve","pair_deny","allow_sender","remove_sender","set_policy"]},code:{type:"string"},sender_id:{type:"string"},policy:{type:"string",enum:["allowlist","open","disabled"]}}},f={required:[],properties:{}},C={grix_query:o,grix_group:a,grix_message_send:p,grix_message_unsend:m,grix_admin:g,grix_reply:y,grix_complete:d,grix_event_ack:c,grix_composing:l,grix_access_control:_,grix_status:f};function B(r,t){const e=C[r];if(!e)return{valid:!1,error:`\u672A\u77E5\u5DE5\u5177: ${r}`};for(const i of e.required)if(t[i]===void 0||t[i]===null)return{valid:!1,error:`\u7F3A\u5C11\u5FC5\u586B\u53C2\u6570: ${i}`};for(const[i,u]of Object.entries(t)){if(u==null)continue;const n=e.properties[i];if(!n)continue;const s=$(i,u,n);if(s)return{valid:!1,error:s}}return{valid:!0}}function $(r,t,e){switch(e.type){case"string":if(typeof t!="string")return`\u53C2\u6570 ${r} \u7C7B\u578B\u9519\u8BEF: \u671F\u671B string\uFF0C\u5B9E\u9645 ${typeof t}`;if(e.maxLength!==void 0&&t.length>e.maxLength)return`\u53C2\u6570 ${r} \u8D85\u8FC7\u6700\u5927\u957F\u5EA6 ${e.maxLength}\uFF0C\u5B9E\u9645 ${t.length}`;if(e.enum&&!e.enum.includes(t))return`\u53C2\u6570 ${r} \u503C "${t}" \u4E0D\u5728\u5141\u8BB8\u8303\u56F4 [${e.enum.join(", ")}]`;break;case"integer":if(typeof t!="number"||!Number.isInteger(t))return`\u53C2\u6570 ${r} \u7C7B\u578B\u9519\u8BEF: \u671F\u671B integer\uFF0C\u5B9E\u9645 ${typeof t=="number"?"\u6D6E\u70B9\u6570":typeof t}`;if(e.minimum!==void 0&&t<e.minimum)return`\u53C2\u6570 ${r} \u503C ${t} \u5C0F\u4E8E\u6700\u5C0F\u503C ${e.minimum}`;if(e.maximum!==void 0&&t>e.maximum)return`\u53C2\u6570 ${r} \u503C ${t} \u5927\u4E8E\u6700\u5927\u503C ${e.maximum}`;if(e.enum&&!e.enum.includes(t))return`\u53C2\u6570 ${r} \u503C ${t} \u4E0D\u5728\u5141\u8BB8\u8303\u56F4 [${e.enum.join(", ")}]`;break;case"boolean":if(typeof t!="boolean")return`\u53C2\u6570 ${r} \u7C7B\u578B\u9519\u8BEF: \u671F\u671B boolean\uFF0C\u5B9E\u9645 ${typeof t}`;break;case"array":if(!Array.isArray(t))return`\u53C2\u6570 ${r} \u7C7B\u578B\u9519\u8BEF: \u671F\u671B array\uFF0C\u5B9E\u9645 ${typeof t}`;if(e.maxItems!==void 0&&t.length>e.maxItems)return`\u53C2\u6570 ${r} \u8D85\u8FC7\u6700\u5927\u5143\u7D20\u6570 ${e.maxItems}\uFF0C\u5B9E\u9645 ${t.length}`;if(e.items)for(let i=0;i<t.length;i++){const u=t[i];if(e.items.type==="string"&&typeof u!="string")return`\u53C2\u6570 ${r}[${i}] \u7C7B\u578B\u9519\u8BEF: \u671F\u671B string\uFF0C\u5B9E\u9645 ${typeof u}`;if(e.items.type==="integer"){if(typeof u!="number"||!Number.isInteger(u))return`\u53C2\u6570 ${r}[${i}] \u7C7B\u578B\u9519\u8BEF: \u671F\u671B integer\uFF0C\u5B9E\u9645 ${typeof u}`;if(e.items.enum&&!e.items.enum.includes(u))return`\u53C2\u6570 ${r}[${i}] \u503C ${u} \u4E0D\u5728\u5141\u8BB8\u8303\u56F4 [${e.items.enum.join(", ")}]`}}break}}export{B as validateToolArgs};
@@ -1 +0,0 @@
1
- import{SessionManager as a}from"./manager.js";export{a as SessionManager};
@@ -1 +0,0 @@
1
- import{EventEmitter as r}from"events";import{AgentProcess as c}from"../agent/process.js";import{AcpClient as l}from"../protocol/acp-client.js";class h extends r{sessions=new Map;async create(s){const e=new c,n=await e.start(s.agent),i=new l;i.on("event",a=>{this.emit("event",a)}),await i.connect({transport:n,sessionId:s.sessionId,authMethod:s.authMethod,initialMode:s.initialMode});const t=i.sessionId,o={id:t,process:e,client:i};return this.sessions.set(t,o),n.on("close",()=>{this.sessions.delete(t),this.emit("sessionClosed",t)}),this.emit("sessionCreated",t),o}get(s){return this.sessions.get(s)}list(){return[...this.sessions.values()]}async close(s){const e=this.sessions.get(s);e&&(this.sessions.delete(s),await e.process.close(),this.emit("sessionClosed",s))}async closeAll(){const s=[...this.sessions.keys()];await Promise.all(s.map(e=>this.close(e)))}get size(){return this.sessions.size}}export{h as SessionManager};
@@ -1 +0,0 @@
1
- import{JsonRpcTransport as o}from"./json-rpc.js";export{o as JsonRpcTransport};
@@ -1,3 +0,0 @@
1
- import{EventEmitter as p}from"events";class w extends p{nextId=1;pending=new Map;writeLock=Promise.resolve();closed=!1;_readable;_writable;_onNotify;_onRequest;constructor(t,e,r,o){super(),this._readable=t,this._writable=e,this._onNotify=r,this._onRequest=o,this.startReadLoop()}setHandlers(t,e){t&&(this._onNotify=t),e&&(this._onRequest=e)}async call(t,e,r){if(this.closed)throw new Error("transport closed");const o=this.nextId++,s=String(o),a=await new Promise((i,d)=>{if(this.pending.set(s,{resolve:i}),r){const n=()=>{this.pending.delete(s),d(new Error(`aborted: ${r.reason??"request cancelled"}`))};if(r.aborted){n();return}r.addEventListener("abort",n,{once:!0});const c=i,h=l=>{r.removeEventListener("abort",n),c(l)};this.pending.set(s,{resolve:h})}this.write({jsonrpc:"2.0",id:o,method:t,params:e??null}).catch(n=>{this.pending.delete(s),d(n)})});if(a.error){const i=new Error(a.error.message);throw i.code=a.error.code,i}return a.result}async notify(t,e){if(this.closed)throw new Error("transport closed");await this.write({jsonrpc:"2.0",method:t,params:e??null})}async respondSuccess(t,e){await this.write({jsonrpc:"2.0",id:t,result:e})}async respondError(t,e,r){await this.write({jsonrpc:"2.0",id:t,error:{code:e,message:r}})}close(){if(!this.closed){this.closed=!0;for(const[t,{resolve:e}]of this.pending)e({error:{code:-32e3,message:"transport closed"}});this.pending.clear(),this._readable.destroy(),this.emit("close")}}startReadLoop(){let t="";this._readable.on("data",e=>{t+=e.toString();const r=t.split(`
2
- `);t=r.pop()??"";for(const o of r){const s=o.trim();s.length!==0&&this.dispatch(s)}}),this._readable.on("end",()=>{t.trim()&&this.dispatch(t.trim()),this.close()}),this._readable.on("error",e=>{this.emit("error",e),this.close()})}dispatch(t){let e;try{e=JSON.parse(t)}catch{return}if(e.id!=null&&!e.method){this.completePending(e.id,e.result,e.error);return}if(e.method&&(e.id==null||e.id===null)){this._onNotify?.(e.method,e.params);return}e.method&&e.id!=null&&this._onRequest?.(e.method,e.id,e.params)}completePending(t,e,r){const o=String(t),s=this.pending.get(o);s&&(this.pending.delete(o),s.resolve({result:e,error:r}))}async write(t){const e=this.writeLock;return this.writeLock=e.then(async()=>new Promise((r,o)=>{const s=JSON.stringify(t)+`
3
- `;this._writable.write(s,"utf-8")?r():(this._writable.once("drain",r),this._writable.once("error",o))})),this.writeLock}}export{w as JsonRpcTransport};