grix-connector 3.19.3 → 3.19.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
- import{fileURLToPath as K}from"node:url";import m from"node:path";import{homedir as I}from"node:os";import{promises as g,statSync as k}from"node:fs";import{EventEmitter as P}from"node:events";import{spawn as E}from"node:child_process";import{AgentProcess as W}from"../../agent/process.js";import{hasChildProcesses as H}from"../../core/runtime/spawn.js";import{applyNativeProviderConfig as G}from"../shared/native-provider-config.js";import{compactToolInputForWire as $}from"../shared/tool-wire-payload.js";import{syncDefaultSkillsToDir as Y,resolveKimiCodeHome as z}from"../../default-skills/index.js";import{prepareKimiLayerPlugin as V}from"../../kimi-plugins/index.js";import{AcpClient as X,AcpAuthRequiredError as Q,isAuthRequiredError as J}from"../../protocol/acp-client.js";import{AgentEventType as f}from"../../types/events.js";import{InternalApiServer as Z}from"../../core/mcp/internal-api-server.js";import{EventResultsStore as ee}from"../../core/persistence/event-results-store.js";import{QuotedMessageStream as A}from"../../core/util/quoted-message-stream.js";import{SafeMarkdownStreamSegmenter as te}from"../../core/text-segmentation/index.js";import{extractAcpTurnInput as ie}from"../../core/protocol/payload-parser.js";import{injectMessageMetadata as se}from"../../core/protocol/message-metadata.js";import{log as r}from"../../core/log/index.js";function M(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 ne=5e3;function x(c,e){const t=e.toolName??"";return e.toolCallId&&(!t||t===e.toolCallId)?c.toolNamesById.get(e.toolCallId)??t:t}function C(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 oe(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 re(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,oe(s.content),s.content,s.description,i.tool_input,i.toolInput,e];for(const o of n){const a=$(o);if(a&&a!==t)return a}return""}import{scanSkills as N}from"../claude/skill-scanner.js";import{checkKiroSessionActivity as ae,checkKimiSessionActivity as le}from"./session-activity.js";import{resolveCliPath as ce,getCliVersion as de}from"../../core/util/cli-probe.js";const R=m.dirname(K(import.meta.url)),O=[{modelId:"deepseek-v4-pro",name:"DeepSeek V4 Pro"},{modelId:"deepseek-v4-flash",name:"DeepSeek V4 Flash"}],w=[{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 _(c,e){const t=Number(process.env[c]);return Number.isFinite(t)&&t>0?t:e}const ue=200,he=60*1e3,pe=600*1e3,S=_("GRIX_ACP_TURN_INACTIVITY_TIMEOUT_MS",900*1e3),B=2,q=5e3,D=80,F=1200*1e3,me=_("GRIX_ACP_BUSY_RETRY_INTERVAL_MS",5e3),U=_("GRIX_ACP_BUSY_WAIT_MAX_MS",1800*1e3),fe=6e4,ge=2e3,ve=12e3,j=3e3,L=3e4,Ie=["[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 ye(c){return!(!(c instanceof Error)||c.code!==-32603||J(c))}function Ae(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 v(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 Ce(c){return c.replace(/\u001b\[[0-9;?]*[ -/]*[@-~]/g,"")}class Ye extends P{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;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;rawEventSeq=0;recoveryContextBySessionId=new Map;quoteProtocolTaughtSessions=new Set;identityInjectedSessions=new Set;orphanBuffer="";orphanQuotedStream=new A;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??m.basename(e.command)).trim().toLowerCase();const s=m.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 ee(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.snapshotAuditBoundary(this.activeRun.eventId),this.clearRunTimers(this.activeRun),this.activeRun=null),this.flushOrphanBuffer(),this.acpClient&&(this.acpClient.clearSettleTimer(),this.acpClient.removeAllListeners(),this.acpClient=null),this.agentProcess&&(await this.agentProcess.close(),this.agentProcess=null),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 Se(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(L))}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=m.resolve(R,"../../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?H(e,[e]):!1}async probe(e){const t=this.config.command||"gemini",i=await ce(t),s=i!==null;let n=null,o;if(s){const p=await de(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=N({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?O:this.isKimi?w:null;if(!t||e&&e.models.length>0)return e;const i=e?.currentModelId??"",s=t.some(n=>n.modelId===i)?i:t[0].modelId;return{modes:e?.modes??[],currentModeId:e?.currentModeId??"",models:[...t],currentModelId:s}}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(h=>({id:h.id,name:h.name}))??[],a=i?.models&&i.models.length>0,l=a?i.models:this.isReasonix?O:this.isKimi?w:[],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(w.map(h=>h.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(h=>({modelId:h.modelId,name:h.name})),u=l.map(h=>({id:h.modelId,displayName:h.name})),T=i?.modes.map(h=>({id:h.id,displayName:h.name}))??[];u.length>0&&!u.some(h=>h.id===n)&&(n=u[0].id);const y={outcome:e,...t?{cwd:t}:{},model_id:n,mode_id:s,currentModelId:n,currentModeId:s,available_models:u,available_modes:T,availableModels:u,availableModes:T,models:p,modes:o};return r.info("acp-adapter",`[toolbar] buildToolbarContext outcome=${e} model_id="${n}" available_models=${JSON.stringify(u.map(h=>h.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)}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==="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:""}}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: ${v(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=N({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(L),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){}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.callbacks.sendEventResult(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.callbacks.sendEventResult(e.event_id,"failed","agent busy");return}if(!this.agentProcess?.alive){this.callbacks.sendEventResult(e.event_id,"failed","agent not alive");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.callbacks.sendEventResult(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.callbacks.sendEventResult(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 A,markdownSegmenter:new te,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=ie(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=se(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.injectRecoveryContext(s.sessionId,l);this.sendPromptWithRetry(s,d)}injectQuoteProtocolOnce(e,t){return!e||this.quoteProtocolTaughtSessions.has(e)?t:(this.quoteProtocolTaughtSessions.add(e),`${Ie}
1
+ import{fileURLToPath as K}from"node:url";import m from"node:path";import{homedir as I}from"node:os";import{promises as g,statSync as k}from"node:fs";import{EventEmitter as P}from"node:events";import{spawn as $}from"node:child_process";import{AgentProcess as W}from"../../agent/process.js";import{hasChildProcesses as H}from"../../core/runtime/spawn.js";import{applyNativeProviderConfig as G}from"../shared/native-provider-config.js";import{compactToolInputForWire as E}from"../shared/tool-wire-payload.js";import{syncDefaultSkillsToDir as Y,resolveKimiCodeHome as z}from"../../default-skills/index.js";import{prepareKimiLayerPlugin as V}from"../../kimi-plugins/index.js";import{AcpClient as X,AcpAuthRequiredError as Q,isAuthRequiredError as J}from"../../protocol/acp-client.js";import{AgentEventType as f}from"../../types/events.js";import{InternalApiServer as Z}from"../../core/mcp/internal-api-server.js";import{EventResultsStore as ee}from"../../core/persistence/event-results-store.js";import{QuotedMessageStream as C}from"../../core/util/quoted-message-stream.js";import{SafeMarkdownStreamSegmenter as te}from"../../core/text-segmentation/index.js";import{extractAcpTurnInput as ie}from"../../core/protocol/payload-parser.js";import{injectMessageMetadata as se}from"../../core/protocol/message-metadata.js";import{log as r}from"../../core/log/index.js";function M(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 ne=5e3;function x(c,e){const t=e.toolName??"";return e.toolCallId&&(!t||t===e.toolCallId)?c.toolNamesById.get(e.toolCallId)??t:t}function A(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 oe(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 re(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,oe(s.content),s.content,s.description,i.tool_input,i.toolInput,e];for(const o of n){const a=E(o);if(a&&a!==t)return a}return""}import{scanSkills as N}from"../claude/skill-scanner.js";import{checkKiroSessionActivity as ae,checkKimiSessionActivity as le}from"./session-activity.js";import{resolveCliPath as ce,getCliVersion as de}from"../../core/util/cli-probe.js";const R=m.dirname(K(import.meta.url)),O=[{modelId:"deepseek-v4-pro",name:"DeepSeek V4 Pro"},{modelId:"deepseek-v4-flash",name:"DeepSeek V4 Flash"}],w=[{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 _(c,e){const t=Number(process.env[c]);return Number.isFinite(t)&&t>0?t:e}const ue=200,he=60*1e3,pe=600*1e3,S=_("GRIX_ACP_TURN_INACTIVITY_TIMEOUT_MS",900*1e3),B=2,F=5e3,q=80,D=1200*1e3,me=_("GRIX_ACP_BUSY_RETRY_INTERVAL_MS",5e3),U=_("GRIX_ACP_BUSY_WAIT_MAX_MS",1800*1e3),fe=6e4,ge=2e3,ve=12e3,j=3e3,L=3e4,Ie=["[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 ye(c){return!(!(c instanceof Error)||c.code!==-32603||J(c))}function Ce(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 v(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 Ae(c){return c.replace(/\u001b\[[0-9;?]*[ -/]*[@-~]/g,"")}class Ye extends P{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;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;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??m.basename(e.command)).trim().toLowerCase();const s=m.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 ee(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.snapshotAuditBoundary(this.activeRun.eventId),this.clearRunTimers(this.activeRun),this.activeRun=null),this.flushOrphanBuffer(),this.acpClient&&(this.acpClient.clearSettleTimer(),this.acpClient.removeAllListeners(),this.acpClient=null),this.agentProcess&&(await this.agentProcess.close(),this.agentProcess=null),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 Se(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(L))}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=m.resolve(R,"../../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?H(e,[e]):!1}async probe(e){const t=this.config.command||"gemini",i=await ce(t),s=i!==null;let n=null,o;if(s){const p=await de(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=N({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?O:this.isKimi?w: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(h=>({id:h.id,name:h.name}))??[],a=i?.models&&i.models.length>0,l=a?i.models:this.isReasonix?O:this.isKimi?w:[],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(w.map(h=>h.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(h=>({modelId:h.modelId,name:h.name})),u=l.map(h=>({id:h.modelId,displayName:h.name})),T=i?.modes.map(h=>({id:h.id,displayName:h.name}))??[];u.length>0&&!u.some(h=>h.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:T,availableModels:u,availableModes:T,models:p,modes:o};return r.info("acp-adapter",`[toolbar] buildToolbarContext outcome=${e} model_id="${n}" available_models=${JSON.stringify(u.map(h=>h.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)}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==="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:""}}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: ${v(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=N({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(L),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){}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.callbacks.sendEventResult(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.callbacks.sendEventResult(e.event_id,"failed","agent busy");return}if(!this.agentProcess?.alive){this.callbacks.sendEventResult(e.event_id,"failed","agent not alive");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.callbacks.sendEventResult(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.callbacks.sendEventResult(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 te,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=ie(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=se(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.injectRecoveryContext(s.sessionId,l);this.sendPromptWithRetry(s,d)}injectQuoteProtocolOnce(e,t){return!e||this.quoteProtocolTaughtSessions.has(e)?t:(this.quoteProtocolTaughtSessions.add(e),`${Ie}
4
4
 
5
5
  ${t}`)}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}"`),`${o.join(`
6
6
  `)}
@@ -8,19 +8,19 @@ ${t}`)}injectIdentityOnce(e,t){if(!e||this.identityInjectedSessions.has(e))retur
8
8
  ${t}`}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})`),`${i}
9
9
 
10
10
  [\u5F53\u524D\u7528\u6237\u6D88\u606F]
11
- ${t}`):t}sendPromptWithRetry(e,t){this.acpClient.send(t).then(()=>{this.activeRun===e&&(e.busyWaiting=!1)}).catch(i=>{if(Ae(i)){this.scheduleAgentBusyRetry(e,t,v(i));return}if(r.error("acp-adapter",`Prompt failed: ${v(i)}`),ye(i)){const n=v(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>=U){r.error("acp-adapter",`agent busy wait exceeded ${Math.round(U/6e4)}min, giving up event=${e.eventId}: ${i}`),e.buffer+=`
11
+ ${t}`):t}sendPromptWithRetry(e,t){this.acpClient.send(t).then(()=>{this.activeRun===e&&(e.busyWaiting=!1)}).catch(i=>{if(Ce(i)){this.scheduleAgentBusyRetry(e,t,v(i));return}if(r.error("acp-adapter",`Prompt failed: ${v(i)}`),ye(i)){const n=v(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>=U){r.error("acp-adapter",`agent busy wait exceeded ${Math.round(U/6e4)}min, giving up event=${e.eventId}: ${i}`),e.buffer+=`
12
12
 
13
- 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(me*2**e.busyRetryCount,fe);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(m.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 W(this.bridgeLog);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 G(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)}`)});try{if(this.isKimi){const s=V({...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 X,this.acpClient.on("event",a=>this.handleAcpEvent(a)),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=async a=>{await this.acpClient.connect({transport:this.agentProcess.transport,authMethod:this.acpAuthMethod,initialMode:this.acpInitialMode,initialModel:this.acpInitialModel,cwd:e||this.resolveCwd(),mcpServers:t,sessionId:a})};let n=!1,o;try{await s(i)}catch(a){if(a instanceof Q){await this.handleAuthRequired(a);return}if(!i)throw this.handleConnectFailure(a),a;if(r.warn("acp-adapter",`Failed to load persisted session ${i}, fallback to session/new: ${v(a)}`),n=!0,o=i,this.config.command==="kiro-cli"&&this.currentAibotSessionId){r.info("acp-adapter",`Building kiro recovery summary: aibotSession=${this.currentAibotSessionId} acpSession=${i}`);const l=await this.buildKiroRecoveryContext(this.currentAibotSessionId,i,e||this.resolveCwd());l?(this.recoveryContextBySessionId.set(this.currentAibotSessionId,l),r.info("acp-adapter",`Recovery context prepared for session ${this.currentAibotSessionId} (chars=${l.length})`)):r.warn("acp-adapter",`Recovery context skipped: no summary generated for acpSession=${i}`)}await s(void 0)}this.sessionConnected=!0,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[a,l]of this.sessionBindings){this.bindingStore&&this.acpClient.sessionId&&this.bindingStore.setAcpSessionId(a,this.acpClient.sessionId);const d=n?"binding_recreated":"binding_ready",p=this.buildToolbarContext(d,l);n&&o&&(p.previous_session_id=o,p.recreated_session_id=this.acpClient.sessionId),this.callbacks.sendUpdateBindingCard(a,"ready",l,p)}}async buildKiroRecoveryContext(e,t,i){try{const s=m.join(I(),".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=m.join(I(),".grix","data","session-summaries");await g.mkdir(o,{recursive:!0});const a=m.join(o,`${e}.md`),l=[`SOURCE_KIRO_SESSION_FILE: ${s}`,"",n.trim(),""].join(`
13
+ 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(me*2**e.busyRetryCount,fe);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(m.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 W(this.bridgeLog);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 G(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=V({...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 X,this.acpClient.on("event",a=>this.handleAcpEvent(a)),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=async a=>{await this.acpClient.connect({transport:this.agentProcess.transport,authMethod:this.acpAuthMethod,initialMode:this.acpInitialMode,initialModel:this.acpInitialModel,cwd:e||this.resolveCwd(),mcpServers:t,sessionId:a})};let n=!1,o;try{await s(i)}catch(a){if(a instanceof Q){await this.handleAuthRequired(a);return}if(!i)throw this.handleConnectFailure(a),a;if(r.warn("acp-adapter",`Failed to load persisted session ${i}, fallback to session/new: ${v(a)}`),n=!0,o=i,this.config.command==="kiro-cli"&&this.currentAibotSessionId){r.info("acp-adapter",`Building kiro recovery summary: aibotSession=${this.currentAibotSessionId} acpSession=${i}`);const l=await this.buildKiroRecoveryContext(this.currentAibotSessionId,i,e||this.resolveCwd());l?(this.recoveryContextBySessionId.set(this.currentAibotSessionId,l),r.info("acp-adapter",`Recovery context prepared for session ${this.currentAibotSessionId} (chars=${l.length})`)):r.warn("acp-adapter",`Recovery context skipped: no summary generated for acpSession=${i}`)}await s(void 0)}this.sessionConnected=!0,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[a,l]of this.sessionBindings){this.bindingStore&&this.acpClient.sessionId&&this.bindingStore.setAcpSessionId(a,this.acpClient.sessionId);const d=n?"binding_recreated":"binding_ready",p=this.buildToolbarContext(d,l);n&&o&&(p.previous_session_id=o,p.recreated_session_id=this.acpClient.sessionId),this.callbacks.sendUpdateBindingCard(a,"ready",l,p)}}async buildKiroRecoveryContext(e,t,i){try{const s=m.join(I(),".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=m.join(I(),".grix","data","session-summaries");await g.mkdir(o,{recursive:!0});const a=m.join(o,`${e}.md`),l=[`SOURCE_KIRO_SESSION_FILE: ${s}`,"",n.trim(),""].join(`
14
14
  `);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(`
15
15
  `)}catch(s){r.warn("acp-adapter",`Failed to build kiro recovery context: ${v(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(`
16
- `)],n=await this.runCommandCapture("kiro-cli",s,t,45e3);if(!n)return;const o=Ce(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(`
16
+ `)],n=await this.runCommandCapture("kiro-cli",s,t,45e3);if(!n)return;const o=Ae(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(`
17
17
  `).filter(d=>!d.includes("Credits:")&&!d.includes("Time:")).join(`
18
- `).trim()}runCommandCapture(e,t,i,s){return new Promise((n,o)=>{const a=E(e,t,{cwd:i,stdio:["ignore","pipe","pipe"]});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: ${v(e)}`),this.acpClient?.removeAllListeners(),this.acpClient=null,this.emit("exit",null)}async startInternalApiAndMcp(){try{this.internalApi||(this.internalApi=new Z,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")?m.join(I(),".qwen","skills"):e.includes("gemini")?m.join(I(),".gemini","skills"):e.includes("kiro")?m.join(I(),".kiro","skills"):this.isReasonix?m.join(I(),".reasonix","skills"):this.isKimi?m.join(z({...process.env,...this.config.env}),"skills"):void 0;if(t){const o=Y(t);o.length>0&&r.info("acp-adapter",`Synced connector skills to ${t}: [${o.join(", ")}]`)}const i=m.resolve(R,"../../mcp/mcp-bridge-server.js"),s=this.internalApi.mcpBridgeWsUrl,n=m.resolve(R,"../../mcp/acp-mcp-server.js");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.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.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:
18
+ `).trim()}runCommandCapture(e,t,i,s){return new Promise((n,o)=>{const a=$(e,t,{cwd:i,stdio:["ignore","pipe","pipe"]});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: ${v(e)}`),this.acpClient?.removeAllListeners(),this.acpClient=null,this.emit("exit",null)}async startInternalApiAndMcp(){try{this.internalApi||(this.internalApi=new Z,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")?m.join(I(),".qwen","skills"):e.includes("gemini")?m.join(I(),".gemini","skills"):e.includes("kiro")?m.join(I(),".kiro","skills"):this.isReasonix?m.join(I(),".reasonix","skills"):this.isKimi?m.join(z({...process.env,...this.config.env}),"skills"):void 0;if(t){const o=Y(t);o.length>0&&r.info("acp-adapter",`Synced connector skills to ${t}: [${o.join(", ")}]`)}const i=m.resolve(R,"../../mcp/mcp-bridge-server.js"),s=this.internalApi.mcpBridgeWsUrl,n=m.resolve(R,"../../mcp/acp-mcp-server.js");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.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.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:
19
19
  ${s}
20
20
 
21
- 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.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"]});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:
21
+ 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.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=$(this.config.command,e.args??[],{env:{...process.env,...this.config.env??{}},stdio:["ignore","pipe","pipe"]});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:
22
22
  ${y[0]}
23
23
 
24
- 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 ${F}ms`)))},F);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:M(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=C(s);if(t.lastToolUse?.toolName===e.toolName&&C(t.lastToolUse.toolInput)===n)break;t.lastToolUse={toolName:e.toolName,toolInput:s},e.toolCallId&&t.sentToolUseIds.add(e.toolCallId);const o=this.trimToolWirePayload?$(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=x(t,e),n=e.content,o=C(n);if(t.lastToolResult?.toolName===s&&C(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<ne)break;t.lastToolProgressSentAt.set(s,n);const a=x(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:{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:M(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}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 i=t.toolCallId,s=re(t.rawParams,e.toolInput,t.toolTitle);this.pendingApprovals.set(i,e.requestId);const n=this.activeRun;n?(n.idleTimer&&(clearTimeout(n.idleTimer),n.idleTimer=null),this.emitRawEventEnvelope(n,{type:"permission_request",payload:{request_id:t.requestId,tool_call_id:i,tool_name:t.toolName,tool_title:t.toolTitle,...s?{tool_input:s}:{},options:t.options}})?r.info("acp-adapter","permission_request forwarded via rawEventEnvelope",{sessionId:n.sessionId,eventId:n.eventId,toolCallId:i,toolName:t.toolName}):(r.info("acp-adapter","permission_request falling back to sendPermissionCard",{sessionId:n.sessionId,eventId:n.eventId,toolCallId:i,toolName:t.toolName}),this.callbacks.sendPermissionCard({eventId:n.eventId,sessionId:n.sessionId,toolCallId:i,toolName:t.toolName,toolTitle:t.toolTitle,toolInput:s,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(i))}emitRawEventEnvelope(e,t){return!e||!this.rawTransport||!this.callbacks.sendRawEventEnvelope?!1:(this.callbacks.sendRawEventEnvelope(e.eventId,e.sessionId,{type:t.type,payload:t.payload,seq:++this.rawEventSeq,at:new Date().toISOString()}),!0)}resetIdleTimer(e,t){e.idleTimer&&clearTimeout(e.idleTimer);const i=t??(e.awaitingToolResult?pe:he),s=e.lastProgressAt+S-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>=S){r.error("acp-adapter",`Turn inactive ${Math.round(t/1e3)}s \u2265 cap ${Math.round(S/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){r.info("acp-adapter",`Idle timer: tool running, ping ok, continuing: ${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.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(S/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(),this.resetIdleTimer(e);return}if(e.consecutivePingFailures++,e.lastIdleCheckAt=Date.now(),e.consecutivePingFailures<B){r.warn("acp-adapter",`Idle timer: ping failed (${e.consecutivePingFailures}/${B}), rechecking in ${q/1e3}s: ${e.eventId}`),this.resetIdleTimer(e,q);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 le(e,t):this.config.command==="kiro-cli"?await ae(e,t):!1}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(),ue))}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>=ve?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()},ge),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 A;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+=j)this.callbacks.sendAgentMessage(t,i.slice(s,s+j))}}suppressOrphanOutput(e){this.orphanSuppressedUntil=Date.now()+e,this.orphanFlushTimer&&(clearTimeout(this.orphanFlushTimer),this.orphanFlushTimer=null),this.orphanBuffer="",this.orphanQuotedStream=new A,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+=`
24
+ 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 ${D}ms`)))},D);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:M(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=A(s);if(t.lastToolUse?.toolName===e.toolName&&A(t.lastToolUse.toolInput)===n)break;t.lastToolUse={toolName:e.toolName,toolInput:s},e.toolCallId&&t.sentToolUseIds.add(e.toolCallId);const o=this.trimToolWirePayload?E(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=x(t,e),n=e.content,o=A(n);if(t.lastToolResult?.toolName===s&&A(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<ne)break;t.lastToolProgressSentAt.set(s,n);const a=x(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:{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:M(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}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 i=t.toolCallId,s=re(t.rawParams,e.toolInput,t.toolTitle);this.pendingApprovals.set(i,e.requestId);const n=this.activeRun;n?(n.idleTimer&&(clearTimeout(n.idleTimer),n.idleTimer=null),this.emitRawEventEnvelope(n,{type:"permission_request",payload:{request_id:t.requestId,tool_call_id:i,tool_name:t.toolName,tool_title:t.toolTitle,...s?{tool_input:s}:{},options:t.options}})?r.info("acp-adapter","permission_request forwarded via rawEventEnvelope",{sessionId:n.sessionId,eventId:n.eventId,toolCallId:i,toolName:t.toolName}):(r.info("acp-adapter","permission_request falling back to sendPermissionCard",{sessionId:n.sessionId,eventId:n.eventId,toolCallId:i,toolName:t.toolName}),this.callbacks.sendPermissionCard({eventId:n.eventId,sessionId:n.sessionId,toolCallId:i,toolName:t.toolName,toolTitle:t.toolTitle,toolInput:s,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(i))}emitRawEventEnvelope(e,t){return!e||!this.rawTransport||!this.callbacks.sendRawEventEnvelope?!1:(this.callbacks.sendRawEventEnvelope(e.eventId,e.sessionId,{type:t.type,payload:t.payload,seq:++this.rawEventSeq,at:new Date().toISOString()}),!0)}resetIdleTimer(e,t){e.idleTimer&&clearTimeout(e.idleTimer);const i=t??(e.awaitingToolResult?pe:he),s=e.lastProgressAt+S-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>=S){r.error("acp-adapter",`Turn inactive ${Math.round(t/1e3)}s \u2265 cap ${Math.round(S/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){r.info("acp-adapter",`Idle timer: tool running, ping ok, continuing: ${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.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(S/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(),this.resetIdleTimer(e);return}if(e.consecutivePingFailures++,e.lastIdleCheckAt=Date.now(),e.consecutivePingFailures<B){r.warn("acp-adapter",`Idle timer: ping failed (${e.consecutivePingFailures}/${B}), 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 le(e,t):this.config.command==="kiro-cli"?await ae(e,t):!1}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(),ue))}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>=ve?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()},ge),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+=j)this.callbacks.sendAgentMessage(t,i.slice(s,s+j))}}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+=`
25
25
 
26
- 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=k(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=k(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<D||this.pendingAutoCompact||this.compacting||(this.pendingAutoCompact=!0,r.info("acp-adapter",`[auto-compact] context_window usedPercentage=${t.toFixed(1)}% >= ${D}%, 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 Se extends P{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{Ye as AcpAdapter};
26
+ 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=k(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=k(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<q||this.pendingAutoCompact||this.compacting||(this.pendingAutoCompact=!0,r.info("acp-adapter",`[auto-compact] context_window usedPercentage=${t.toFixed(1)}% >= ${q}%, 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 Se extends P{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{Ye as AcpAdapter};
@@ -1,10 +1,10 @@
1
- import{execFile as _}from"node:child_process";import{promisify as b}from"node:util";import{EventEmitter as v}from"node:events";import{stat as P}from"node:fs/promises";import{mkdirSync as w,writeFileSync as T,unlinkSync as A,statSync as u,readFileSync as k,existsSync as $}from"node:fs";import{join as p,resolve as C}from"node:path";import{fileURLToPath as R}from"node:url";import{tmpdir as M,homedir as m}from"node:os";import{resolveCommandPath as F,spawnCommand as B,killProcessGroup as S,hasChildProcesses as D}from"../../core/runtime/spawn.js";import{InternalApiServer as N}from"../../core/mcp/internal-api-server.js";import{IdentityInjector as G}from"../shared/identity-injector.js";import{compactToolInputForWire as L}from"../shared/tool-wire-payload.js";import{syncDefaultSkillsToDir as O}from"../../default-skills/index.js";import{PiTransport as j}from"./pi-transport.js";import{log as a}from"../../core/log/index.js";import{scanSkills as q}from"../claude/skill-scanner.js";import{SessionBindingStore as U}from"../../core/persistence/session-binding-store.js";import{splitTextForAibotProtocol as z}from"../../core/protocol/index.js";import{buildSimpleProbeReport as H}from"../shared/probe-util.js";import{piGrixProviderId as W,removePiGrixProvider as Y,writePiGrixProvider as X}from"./pi-provider-config.js";const I=new Map;function J(){const c=p(m(),".pi","agent","mcp.json");try{if(!$(c))return{};const e=JSON.parse(k(c,"utf8"))?.mcpServers;return!e||typeof e!="object"||Array.isArray(e)?{}:e}catch(t){return a.warn("pi-adapter",`Failed to read Pi user MCP config ${c}: ${t instanceof Error?t.message:String(t)}`),{}}}async function V(c){const t=I.get(c);if(t!==void 0)return t;let e=!1;try{const s=b(_),i=process.platform==="win32",n=i&&/\s/.test(c)&&!c.startsWith('"')?`"${c}"`:c,{stdout:r,stderr:d}=await s(n,["--help"],{timeout:5e3,encoding:"utf-8",...i?{shell:!0}:{}});e=/--mcp-config\b/.test(`${r??""}${d??""}`)}catch{e=!1}return I.set(c,e),a.info("pi-adapter",`Pi --mcp-config \u652F\u6301\u63A2\u6D4B: ${e?"\u652F\u6301":"\u4E0D\u652F\u6301"} (${c})`),e}class K extends v{adapterSessionId;constructor(t){super(),this.adapterSessionId=t}emitDone(t){this.emit("done",t)}emitError(t){if(this.listenerCount("error")===0){a.warn("pi-adapter",`Prompt handle error (no listeners): ${t.message}`);return}this.emit("error",t)}async cancel(){}}const Q=12e4,Z=90*1e3,x=1800*1e3;function E(c){try{return process.kill(c,0),!0}catch(t){return t.code==="EPERM"}}const tt=500,et=2e3,f=3,st=500;function it(c){return new Promise(t=>setTimeout(t,c))}class y extends v{type="pi";config;callbacks;process=null;transport=new j;alive=!1;stopped=!1;internalApi=null;mcpConfigPath=null;identity;piSessionPath=null;activeEventId=null;activeSessionId=null;activeAuditCapture=null;completedAuditBoundaries=new Map;isStreaming=!1;streamSeq=0;clientMsgSeq=0;activeClientMsgId=null;thinkingSeq=0;textBuffer="";emittedTextByIndex=new Map;textFlushTimer=null;activeRunError=null;idleTimer=null;lastRpcAt=0;livenessExtendStartAt;toolCallsInFlight=new Set;composingSessionId=null;composingInterval=null;doneGuardTimer=null;bindingStore=null;aibotSessionId="";sessionReadyPromise=null;provider;agentName="";constructor(t,e){super(),this.config=t,this.callbacks=e,this.identity=new G("pi-adapter",e.getAgentProfile);const s=t.options??{};this.aibotSessionId=String(s.aibotSessionId??"").trim(),this.agentName=String(s.agentName??"").trim(),this.provider=s.provider,this.bindingStore=s.bindingStore instanceof U?s.bindingStore:null,this.bindingStore&&this.aibotSessionId&&(this.piSessionPath=this.bindingStore.getPiSessionPath(this.aibotSessionId)??null)}async start(){await this.startInternalApi(),await this.spawnPi(),await this.ensureSessionReady(),a.info("pi-adapter",`Ready (pid=${this.process?.pid})`),await this.syncModelBinding()}async syncModelBinding(){if(this.aibotSessionId)for(let t=1;t<=f;t++)try{const[e,s]=await Promise.all([this.transport.send("get_available_models"),this.transport.send("get_state")]),i=e.data?.models;if(!i||i.length===0){if(t<f){await it(st);continue}a.warn("pi-adapter",`syncModelBinding: empty model list after ${f} attempts, giving up`);return}const n=s.data?.model,r={available_models:i.map(d=>({id:d.id,display_name:d.name??d.id,...d.provider?{provider:d.provider}:{}}))};n?.id&&(r.model_id=n.id),n?.provider&&(r.model_provider=n.provider),this.callbacks.sendUpdateBindingCard(this.aibotSessionId,this.isStreaming?"composing":"ready",this.resolveCwd(),r),a.info("pi-adapter",`synced model binding: ${i.length} models, current=${n?.id??"unknown"} (attempt ${t})`);return}catch(e){a.warn("pi-adapter",`syncModelBinding failed (non-fatal): ${e instanceof Error?e.message:String(e)}`);return}}async stop(){if(this.stopped=!0,this.alive=!1,this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.cancelDoneGuard(),this.transport.close(),this.internalApi&&(await this.internalApi.stop(),this.internalApi=null),this.mcpConfigPath){try{A(this.mcpConfigPath)}catch{}this.mcpConfigPath=null}if(this.process){const t=this.process;try{S(t,"SIGTERM")}catch{}const e=setTimeout(()=>{try{S(t,"SIGKILL")}catch{}},5e3);t.on("exit",()=>clearTimeout(e)),this.process=null}}isAlive(){return this.alive}async createSession(t){this.sessionReadyPromise=null,await this.createNewSession();const e=this.piSessionPath||`pi-${Date.now()}`;return a.info("pi-adapter",`Session created: ${e} (path=${this.piSessionPath})`),e}async resumeSession(t,e){await this.switchSession()}onAgentProfileChanged(){this.identity.onProfileChanged()}async destroySession(t){this.identity.forgetSession(t),this.piSessionPath=null,this.sessionReadyPromise=null,this.persistPiSessionPath(void 0)}sendPrompt(t){const e=new K(t.adapterSessionId),s=this.buildPromptTextFromRequest(t);return this.ensureSessionReady().then(()=>this.transport.send("prompt",{message:s})).then(i=>{i.success||e.emitDone({status:"failed",error:i.error})}).catch(i=>{e.emitError(i instanceof Error?i:new Error(String(i)))}),e}async cancel(t){try{await this.transport.send("abort")}catch{}}deliverInboundEvent(t){const{event_id:e,session_id:s,content:i}=t,n=this.buildPromptText(t);if(this.isStreaming){if(this.activeEventId&&this.activeEventId!==e){const r=this.activeEventId;a.info("pi-adapter",`steer: cancel ${r} -> ${e}`),this.flushTextBuffer(),this.callbacks.sendEventResult(r,"canceled","steered to new event"),this.activeAuditCapture?.eventId===r&&(this.snapshotAuditBoundary(r),this.emit("eventDone",r))}this.activeEventId=e,this.activeSessionId=s,this.beginAuditCapture(t),this.resetRunStreamState(),this.startComposing(s,e),this.lastRpcAt=Date.now(),this.livenessExtendStartAt=void 0,this.resetIdleTimer(),this.transport.send("prompt",{message:n,streamingBehavior:"steer"}).catch(r=>{a.error("pi-adapter",`steer failed: ${r}`),this.callbacks.sendEventResult(e,"failed",String(r))})}else a.info("pi-adapter",`prompt: event=${e} session=${s}`),this.activeEventId=e,this.activeSessionId=s,this.beginAuditCapture(t),this.isStreaming=!0,this.resetRunStreamState(),this.startComposing(s,e),this.lastRpcAt=Date.now(),this.livenessExtendStartAt=void 0,this.resetIdleTimer(),this.transport.send("prompt",{message:n}).then(r=>{r.success||(a.error("pi-adapter",`prompt rejected: ${r.error}`),this.isStreaming=!1,this.stopComposing(),this.stopIdleTimer(),this.callbacks.sendEventResult(e,"failed",r.error),this.clearActive())}).catch(r=>{a.error("pi-adapter",`prompt error: ${r}`),this.isStreaming=!1,this.stopComposing(),this.stopIdleTimer(),this.callbacks.sendEventResult(e,"failed",String(r)),this.clearActive()})}deliverStopEvent(t,e){if(this.activeEventId===t){a.info("pi-adapter",`stop: event=${t}, releasing busy immediately`),this.transport.send("abort").catch(()=>{}),this.flushTextBuffer();const s=this.nextStreamSeq(),i=this.activeClientMsgId??void 0;this.callbacks.sendStreamChunk(t,this.activeSessionId??"","",s,!0,i),this.isStreaming=!1,this.stopComposing(),this.stopIdleTimer(),this.callbacks.sendEventResult(t,"canceled","stopped by user"),this.clearActive()}}async handleLocalAction(t){const e=t.action_id;switch(t.action_type){case"set_model":{const s=t.params??{},i=(s.modelId??s.model_id??"").trim();if(!i)return this.callbacks.sendLocalActionResult(e,"failed",void 0,"invalid_model","model_id is required"),{handled:!0,kind:"set_model_error"};try{let n=s.provider?.trim();return n||(n=(await this.transport.send("get_available_models")).data?.models?.find(l=>l.id===i)?.provider?.trim()),n?(await this.transport.send("set_model",{provider:n,modelId:i}),this.callbacks.sendLocalActionResult(e,"ok",{outcome:"model_set",provider:n,modelId:i}),this.syncModelBinding(),{handled:!0,kind:"set_model"}):(this.callbacks.sendLocalActionResult(e,"failed",void 0,"model_not_found",`No provider for model: ${i}`),{handled:!0,kind:"set_model_error"})}catch(n){return this.callbacks.sendLocalActionResult(e,"failed",void 0,"set_model_error",n instanceof Error?n.message:String(n)),{handled:!0,kind:"set_model_error"}}}case"get_context":try{const s=await this.transport.send("get_state");return this.callbacks.sendLocalActionResult(e,"ok",{state:s}),{handled:!0,kind:"get_context"}}catch(s){return this.callbacks.sendLocalActionResult(e,"failed",void 0,"get_context_error",s instanceof Error?s.message:String(s)),{handled:!1,kind:""}}case"pi_extension_ui_response":return this.transport.sendNoWait({type:"extension_ui_response",...t.params??{}}),this.callbacks.sendLocalActionResult(e,"ok"),{handled:!0,kind:"extension_ui_response"};default:return{handled:!1,kind:""}}}setPermissionHandler(t){}async ping(t){try{return await this.transport.send("get_state"),!0}catch{return!1}}getStatus(){return{alive:this.alive,busy:this.isStreaming,sessions:this.piSessionPath?1:0}}getActiveEventIds(){return this.activeEventId?[this.activeEventId]:[]}takeAuditBoundary(t){const e=this.completedAuditBoundaries.get(t);return e&&this.completedAuditBoundaries.delete(t),e}clearActiveEventForShutdown(){this.stopIdleTimer(),this.activeEventId&&this.snapshotAuditBoundary(this.activeEventId),this.activeEventId=null}getMcpConfig(){if(!this.internalApi)return null;const t=C(R(import.meta.url),"../../../mcp/acp-mcp-server.js");return{name:"grix-connector-tools",command:process.execPath,args:[t,"--api-url",this.internalApi.url]}}async hasBackgroundWork(){const t=this.process?.pid;return t?D(t,[t]):!1}async probe(t){const e=this.getStatus();return{...await H(this.config.command||"pi",{alive:e.alive,busy:e.busy,started:!!this.process},t),session:this.probeSessionRecord()}}probeSessionRecord(){const t=this.piSessionPath;if(!t)return{recordPath:null,lastActivityMs:null,freshMs:null};try{const e=u(t);return{recordPath:t,lastActivityMs:e.mtimeMs,freshMs:Date.now()-e.mtimeMs}}catch{return{recordPath:t,lastActivityMs:null,freshMs:null}}}getSupportedCommands(){return[{name:"model",description:"List or set model",args:"[provider:model_id]"},{name:"interrupt",description:"Interrupt current run"},{name:"status",description:"Show session status"},{name:"skills",description:"List available skills"}]}async execCommand(t,e,s){try{if(!this.alive)return{status:"failed",message:"Pi process not running"};switch(t){case"model":{const i=e.trim();if(i){const d=i.indexOf(":");if(d<1)return{status:"failed",message:"Format: provider:model_id"};const l=i.slice(0,d),h=i.slice(d+1);if(!h)return{status:"failed",message:"Format: provider:model_id"};const o=await this.transport.send("set_model",{provider:l,modelId:h});return o?.success?{status:"ok",message:`Model set to ${l}:${h}`}:{status:"failed",message:`Failed to set model: ${o?.error??"unknown error"}`}}const r=(await this.transport.send("get_available_models"))?.data?.models;return r&&r.length>0?{status:"ok",message:`Available models:
1
+ import{execFile as w}from"node:child_process";import{promisify as P}from"node:util";import{EventEmitter as S}from"node:events";import{stat as b}from"node:fs/promises";import{mkdirSync as T,writeFileSync as $,unlinkSync as A,statSync as m,readFileSync as k,existsSync as C}from"node:fs";import{join as u,resolve as R}from"node:path";import{fileURLToPath as M}from"node:url";import{tmpdir as F,homedir as f}from"node:os";import{resolveCommandPath as B,spawnCommand as D,killProcessGroup as I,hasChildProcesses as N}from"../../core/runtime/spawn.js";import{InternalApiServer as G}from"../../core/mcp/internal-api-server.js";import{IdentityInjector as L}from"../shared/identity-injector.js";import{compactToolInputForWire as O}from"../shared/tool-wire-payload.js";import{syncDefaultSkillsToDir as j}from"../../default-skills/index.js";import{PiTransport as q}from"./pi-transport.js";import{log as a}from"../../core/log/index.js";import{scanSkills as U}from"../claude/skill-scanner.js";import{SessionBindingStore as z}from"../../core/persistence/session-binding-store.js";import{splitTextForAibotProtocol as H}from"../../core/protocol/index.js";import{buildSimpleProbeReport as W}from"../shared/probe-util.js";import{piGrixProviderId as Y,removePiGrixProvider as J,writePiGrixProvider as V}from"./pi-provider-config.js";const x=new Map;function X(){const d=u(f(),".pi","agent","mcp.json");try{if(!C(d))return{};const e=JSON.parse(k(d,"utf8"))?.mcpServers;return!e||typeof e!="object"||Array.isArray(e)?{}:e}catch(t){return a.warn("pi-adapter",`Failed to read Pi user MCP config ${d}: ${t instanceof Error?t.message:String(t)}`),{}}}async function K(d){const t=x.get(d);if(t!==void 0)return t;let e=!1;try{const s=P(w),i=process.platform==="win32",n=i&&/\s/.test(d)&&!d.startsWith('"')?`"${d}"`:d,{stdout:r,stderr:c}=await s(n,["--help"],{timeout:5e3,encoding:"utf-8",...i?{shell:!0}:{}});e=/--mcp-config\b/.test(`${r??""}${c??""}`)}catch{e=!1}return x.set(d,e),a.info("pi-adapter",`Pi --mcp-config \u652F\u6301\u63A2\u6D4B: ${e?"\u652F\u6301":"\u4E0D\u652F\u6301"} (${d})`),e}class Q extends S{adapterSessionId;constructor(t){super(),this.adapterSessionId=t}emitDone(t){this.emit("done",t)}emitError(t){if(this.listenerCount("error")===0){a.warn("pi-adapter",`Prompt handle error (no listeners): ${t.message}`);return}this.emit("error",t)}async cancel(){}}const Z=12e4,tt=90*1e3,E=1800*1e3;function y(d){try{return process.kill(d,0),!0}catch(t){return t.code==="EPERM"}}const et=500,st=2e3,g=3,it=500;function nt(d){return new Promise(t=>setTimeout(t,d))}function p(d){if(d===void 0)return"undefined";if(d===null)return"null";if(typeof d=="string")return d.length>500?`${d.slice(0,500)}...`:d;try{const t=JSON.stringify(d);return t.length>500?`${t.slice(0,500)}...`:t}catch{return String(d)}}class _ extends S{type="pi";config;callbacks;process=null;transport=new q;alive=!1;stopped=!1;internalApi=null;mcpConfigPath=null;identity;piSessionPath=null;activeEventId=null;activeSessionId=null;activeAuditCapture=null;completedAuditBoundaries=new Map;isStreaming=!1;streamSeq=0;clientMsgSeq=0;activeClientMsgId=null;thinkingSeq=0;textBuffer="";emittedTextByIndex=new Map;textFlushTimer=null;activeRunError=null;idleTimer=null;lastRpcAt=0;livenessExtendStartAt;toolCallsInFlight=new Set;composingSessionId=null;composingInterval=null;doneGuardTimer=null;bindingStore=null;aibotSessionId="";sessionReadyPromise=null;provider;agentName="";constructor(t,e){super(),this.config=t,this.callbacks=e,this.identity=new L("pi-adapter",e.getAgentProfile);const s=t.options??{};this.aibotSessionId=String(s.aibotSessionId??"").trim(),this.agentName=String(s.agentName??"").trim(),this.provider=s.provider,this.bindingStore=s.bindingStore instanceof z?s.bindingStore:null,this.bindingStore&&this.aibotSessionId&&(this.piSessionPath=this.bindingStore.getPiSessionPath(this.aibotSessionId)??null)}async start(){await this.startInternalApi(),await this.spawnPi(),await this.ensureSessionReady(),a.info("pi-adapter",`Ready (pid=${this.process?.pid})`),await this.syncModelBinding()}async syncModelBinding(){if(this.aibotSessionId)for(let t=1;t<=g;t++)try{const[e,s]=await Promise.all([this.transport.send("get_available_models"),this.transport.send("get_state")]),i=e.data?.models;if(!i||i.length===0){if(t<g){await nt(it);continue}a.warn("pi-adapter",`syncModelBinding: empty model list after ${g} attempts, giving up`);return}const n=s.data?.model,r={available_models:i.map(c=>({id:c.id,display_name:c.name??c.id,...c.provider?{provider:c.provider}:{}}))};n?.id&&(r.model_id=n.id),n?.provider&&(r.model_provider=n.provider),this.callbacks.sendUpdateBindingCard(this.aibotSessionId,this.isStreaming?"composing":"ready",this.resolveCwd(),r),a.info("pi-adapter",`synced model binding: ${i.length} models, current=${n?.id??"unknown"} (attempt ${t})`);return}catch(e){a.warn("pi-adapter",`syncModelBinding failed (non-fatal): ${e instanceof Error?e.message:String(e)}`);return}}async stop(){if(this.stopped=!0,this.alive=!1,this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.cancelDoneGuard(),this.transport.close(),this.internalApi&&(await this.internalApi.stop(),this.internalApi=null),this.mcpConfigPath){try{A(this.mcpConfigPath)}catch{}this.mcpConfigPath=null}if(this.process){const t=this.process;try{I(t,"SIGTERM")}catch{}const e=setTimeout(()=>{try{I(t,"SIGKILL")}catch{}},5e3);t.on("exit",()=>clearTimeout(e)),this.process=null}}isAlive(){return this.alive}async createSession(t){this.sessionReadyPromise=null,await this.createNewSession();const e=this.piSessionPath||`pi-${Date.now()}`;return a.info("pi-adapter",`Session created: ${e} (path=${this.piSessionPath})`),e}async resumeSession(t,e){await this.switchSession()}onAgentProfileChanged(){this.identity.onProfileChanged()}async destroySession(t){this.identity.forgetSession(t),this.piSessionPath=null,this.sessionReadyPromise=null,this.persistPiSessionPath(void 0)}sendPrompt(t){const e=new Q(t.adapterSessionId),s=this.buildPromptTextFromRequest(t);return this.ensureSessionReady().then(()=>this.transport.send("prompt",{message:s})).then(i=>{i.success||e.emitDone({status:"failed",error:i.error})}).catch(i=>{e.emitError(i instanceof Error?i:new Error(String(i)))}),e}async cancel(t){try{await this.transport.send("abort")}catch{}}deliverInboundEvent(t){const{event_id:e,session_id:s,content:i}=t,n=this.buildPromptText(t);if(this.isStreaming){if(this.activeEventId&&this.activeEventId!==e){const r=this.activeEventId;a.info("pi-adapter",`steer: cancel ${r} -> ${e}`),this.flushTextBuffer(),this.callbacks.sendEventResult(r,"canceled","steered to new event"),this.activeAuditCapture?.eventId===r&&(this.snapshotAuditBoundary(r),this.emit("eventDone",r))}this.activeEventId=e,this.activeSessionId=s,this.beginAuditCapture(t),this.resetRunStreamState(),this.startComposing(s,e),this.lastRpcAt=Date.now(),this.livenessExtendStartAt=void 0,this.resetIdleTimer(),this.transport.send("prompt",{message:n,streamingBehavior:"steer"}).catch(r=>{a.error("pi-adapter",`steer failed: ${r}`),this.callbacks.sendEventResult(e,"failed",String(r))})}else a.info("pi-adapter",`prompt: event=${e} session=${s}`),this.activeEventId=e,this.activeSessionId=s,this.beginAuditCapture(t),this.isStreaming=!0,this.resetRunStreamState(),this.startComposing(s,e),this.lastRpcAt=Date.now(),this.livenessExtendStartAt=void 0,this.resetIdleTimer(),this.transport.send("prompt",{message:n}).then(r=>{r.success||(a.error("pi-adapter",`prompt rejected: ${r.error}`),this.isStreaming=!1,this.stopComposing(),this.stopIdleTimer(),this.callbacks.sendEventResult(e,"failed",r.error),this.clearActive())}).catch(r=>{a.error("pi-adapter",`prompt error: ${r}`),this.isStreaming=!1,this.stopComposing(),this.stopIdleTimer(),this.callbacks.sendEventResult(e,"failed",String(r)),this.clearActive()})}deliverStopEvent(t,e){if(this.activeEventId===t){a.info("pi-adapter",`stop: event=${t}, releasing busy immediately`),this.transport.send("abort").catch(()=>{}),this.flushTextBuffer();const s=this.nextStreamSeq(),i=this.activeClientMsgId??void 0;this.callbacks.sendStreamChunk(t,this.activeSessionId??"","",s,!0,i),this.isStreaming=!1,this.stopComposing(),this.stopIdleTimer(),this.callbacks.sendEventResult(t,"canceled","stopped by user"),this.clearActive()}}async handleLocalAction(t){const e=t.action_id;switch(t.action_type){case"set_model":{const s=t.params??{},i=(s.modelId??s.model_id??"").trim();if(!i)return this.callbacks.sendLocalActionResult(e,"failed",void 0,"invalid_model","model_id is required"),{handled:!0,kind:"set_model_error"};try{let n=s.provider?.trim();return n||(n=(await this.transport.send("get_available_models")).data?.models?.find(l=>l.id===i)?.provider?.trim()),n?(await this.transport.send("set_model",{provider:n,modelId:i}),this.callbacks.sendLocalActionResult(e,"ok",{outcome:"model_set",provider:n,modelId:i}),this.syncModelBinding(),{handled:!0,kind:"set_model"}):(this.callbacks.sendLocalActionResult(e,"failed",void 0,"model_not_found",`No provider for model: ${i}`),{handled:!0,kind:"set_model_error"})}catch(n){return this.callbacks.sendLocalActionResult(e,"failed",void 0,"set_model_error",n instanceof Error?n.message:String(n)),{handled:!0,kind:"set_model_error"}}}case"get_context":try{const s=await this.transport.send("get_state");return this.callbacks.sendLocalActionResult(e,"ok",{state:s}),{handled:!0,kind:"get_context"}}catch(s){return this.callbacks.sendLocalActionResult(e,"failed",void 0,"get_context_error",s instanceof Error?s.message:String(s)),{handled:!1,kind:""}}case"pi_extension_ui_response":return this.transport.sendNoWait({type:"extension_ui_response",...t.params??{}}),this.callbacks.sendLocalActionResult(e,"ok"),{handled:!0,kind:"extension_ui_response"};default:return{handled:!1,kind:""}}}setPermissionHandler(t){}async ping(t){try{return await this.transport.send("get_state"),!0}catch{return!1}}getStatus(){return{alive:this.alive,busy:this.isStreaming,sessions:this.piSessionPath?1:0}}getActiveEventIds(){return this.activeEventId?[this.activeEventId]:[]}takeAuditBoundary(t){const e=this.completedAuditBoundaries.get(t);return e&&this.completedAuditBoundaries.delete(t),e}clearActiveEventForShutdown(){this.stopIdleTimer(),this.activeEventId&&this.snapshotAuditBoundary(this.activeEventId),this.activeEventId=null}getMcpConfig(){if(!this.internalApi)return null;const t=R(M(import.meta.url),"../../../mcp/acp-mcp-server.js");return{name:"grix-connector-tools",command:process.execPath,args:[t,"--api-url",this.internalApi.url]}}async hasBackgroundWork(){const t=this.process?.pid;return t?N(t,[t]):!1}async probe(t){const e=this.getStatus();return{...await W(this.config.command||"pi",{alive:e.alive,busy:e.busy,started:!!this.process},t),session:this.probeSessionRecord()}}probeSessionRecord(){const t=this.piSessionPath;if(!t)return{recordPath:null,lastActivityMs:null,freshMs:null};try{const e=m(t);return{recordPath:t,lastActivityMs:e.mtimeMs,freshMs:Date.now()-e.mtimeMs}}catch{return{recordPath:t,lastActivityMs:null,freshMs:null}}}getSupportedCommands(){return[{name:"model",description:"List or set model",args:"[provider:model_id]"},{name:"interrupt",description:"Interrupt current run"},{name:"status",description:"Show session status"},{name:"skills",description:"List available skills"}]}async execCommand(t,e,s){try{if(!this.alive)return{status:"failed",message:"Pi process not running"};switch(t){case"model":{const i=e.trim();if(i){const c=i.indexOf(":");if(c<1)return{status:"failed",message:"Format: provider:model_id"};const l=i.slice(0,c),h=i.slice(c+1);if(!h)return{status:"failed",message:"Format: provider:model_id"};const o=await this.transport.send("set_model",{provider:l,modelId:h});return o?.success?{status:"ok",message:`Model set to ${l}:${h}`}:{status:"failed",message:`Failed to set model: ${o?.error??"unknown error"}`}}const r=(await this.transport.send("get_available_models"))?.data?.models;return r&&r.length>0?{status:"ok",message:`Available models:
2
2
  ${r.map(l=>` ${l.provider??"unknown"}:${l.id} (${l.name??l.id})`).join(`
3
- `)}`,data:r}:{status:"ok",message:"No models available",data:[]}}case"interrupt":return this.isStreaming?(await this.transport.send("abort"),this.activeEventId&&this.callbacks.sendEventResult(this.activeEventId,"canceled","interrupted"),this.clearActive(),{status:"ok",message:"Run interrupted"}):{status:"failed",message:"No active run to interrupt"};case"status":{const i=this.getStatus();return{status:"ok",message:`Alive: ${i.alive}, Busy: ${i.busy}, Session: ${this.piSessionPath??"none"}`,data:{alive:i.alive,busy:i.busy,sessions:i.sessions,sessionPath:this.piSessionPath}}}case"skills":{const i=q({mode:"pi"}),n=i.map(r=>`- ${r.name}${r.trigger?` (${r.trigger})`:""} [${r.source}]: ${r.description}`);return{status:"ok",message:n.length>0?n.join(`
4
- `):"No skills found",data:i}}default:return{status:"unsupported",message:`Unknown command: ${t}`}}}catch(i){return{status:"failed",message:i instanceof Error?i.message:String(i)}}}resolveCwd(){if(this.bindingStore&&this.aibotSessionId){const e=this.bindingStore.get(this.aibotSessionId);if(e?.cwd)return e.cwd}const t=(this.config.options??{}).cwd;return typeof t=="string"&&t?t:process.cwd()}async startInternalApi(){try{this.internalApi=new N,this.internalApi.setInvokeHandler(async(r,d,l,h)=>this.callbacks.agentInvoke(r,d,h)),await this.internalApi.start(0),a.info("pi-adapter",`Internal API started at ${this.internalApi.url}`);const t=this.getMcpConfig(),e=J(),s=p(M(),"grix-pi-mcp");w(s,{recursive:!0}),this.mcpConfigPath=p(s,`mcp-${process.pid}-${Date.now()}.json`),T(this.mcpConfigPath,JSON.stringify({mcpServers:{...e,[t.name]:{command:t.command,args:t.args,directTools:!0,lifecycle:"eager"}}}),"utf8"),a.info("pi-adapter",`MCP config written to ${this.mcpConfigPath}`);const i=p(m(),".pi","agent","skills"),n=O(i);n.length>0&&a.info("pi-adapter",`Synced connector skills to ${i}: [${n.join(", ")}]`)}catch(t){a.warn("pi-adapter",`Failed to start MCP tools (non-fatal): ${t instanceof Error?t.message:String(t)}`)}}async spawnPi(){const t=this.config.command||"pi",e=F(t,typeof process.env.PATH=="string"?process.env.PATH:void 0),s=this.config.args??[],n=s.some(o=>o.startsWith("--mode"))?[...s]:["--mode","rpc",...s],r=String(this.config.env?.PI_CODING_AGENT_DIR??"").trim()||p(m(),".pi","agent"),d=W(this.agentName);this.provider?await X(r,d,this.provider)?(n.includes("--provider")||n.push("--provider",d),!n.includes("--model")&&this.provider.model?.trim()&&n.push("--model",this.provider.model.trim())):a.warn("pi-adapter","provider config incomplete (baseUrl/model required); Grix provider not applied"):await Y(r,d).then(o=>{o&&a.info("pi-adapter",`Removed stale Grix provider "${d}" from models.json`)}).catch(o=>{a.warn("pi-adapter",`Failed to clean up Grix provider config: ${o instanceof Error?o.message:String(o)}`)}),this.mcpConfigPath&&!n.some(o=>o==="--mcp-config")&&(await V(e)?n.push("--mcp-config",this.mcpConfigPath):a.warn("pi-adapter","\u5F53\u524D Pi \u4E0D\u652F\u6301 --mcp-config\uFF08\u672A\u68C0\u6D4B\u5230 pi-mcp-adapter \u6269\u5C55\uFF09\uFF0C\u8DF3\u8FC7 MCP \u6CE8\u5165\u4EE5\u907F\u514D\u542F\u52A8\u5931\u8D25"));const l={...process.env,...this.config.env},h=this.resolveCwd();a.info("pi-adapter",`Spawning: ${e} ${n.join(" ")}`);try{if(!(await P(h)).isDirectory())throw new Error(`Bound path is not a directory: ${h}`)}catch(o){throw String(o?.code??"")==="ENOENT"?new Error(`Bound directory does not exist: ${h}. Please rebind with /grix open <valid-directory>.`):o}try{this.process=B(e,n,{env:l,cwd:h}).process}catch(o){throw a.error("pi-adapter",`PI spawn threw: ${o}`),this.alive=!1,o}this.process.on("error",o=>{a.error("pi-adapter",`Spawn error: ${o.message}`),this.alive=!1,this.transport.close(),this.activeEventId&&(this.callbacks.sendEventResult(this.activeEventId,"failed",`Spawn error: ${o.message}`),this.clearActive()),this.stopped||this.emit("exit",1)}),this.process.on("exit",o=>{a.info("pi-adapter",`PI process exited (code=${o})`),this.alive=!1,this.transport.close(),this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.activeEventId&&this.callbacks.sendEventResult(this.activeEventId,"failed",`PI process exited (code=${o})`),this.isStreaming=!1,this.clearActive(),this.stopped||this.emit("exit",o??1)}),this.process.stderr?.on("data",o=>{const g=o.toString().trim();g&&a.info("pi-adapter",`[pi stderr] ${g}`)}),this.transport.on("event",o=>this.handlePiEvent(o)),this.transport.bind(this.process.stdin,this.process.stdout),this.alive=!0}handlePiEvent(t){if(this.stopped)return;if(this.lastRpcAt=Date.now(),t.type==="tool_execution_start"){const s=t;s.toolCallId&&this.toolCallsInFlight.add(s.toolCallId)}else if(t.type==="tool_execution_end"){const s=t;s.toolCallId&&this.toolCallsInFlight.delete(s.toolCallId)}switch(this.resetIdleTimer(),t.type){case"message_update":{const s=t.assistantMessageEvent;if(!s)break;const i=s.type;if(i==="text_delta"){const n=s.delta;if(n){const r=typeof s.contentIndex=="number"?s.contentIndex:0;this.rememberEmittedText(r,n),this.appendText(n)}}else if(i==="text_end"){const n=s.content,r=typeof s.contentIndex=="number"?s.contentIndex:0;n&&this.appendMissingText(r,n)}else if(i==="thinking_delta"){const n=s.delta;n&&this.activeEventId&&this.activeSessionId&&(this.thinkingSeq++,this.callbacks.sendThinking(this.activeEventId,this.activeSessionId,n))}else if(i==="done"||i==="error"){if(this.flushTextBuffer(),i==="error"){const n=this.piMessageError(s.error,s.reason);this.recordRunError(n)}this.scheduleDoneGuard()}break}case"message_end":{const s=t.message;s?.role==="assistant"&&s.stopReason==="error"&&(this.recordRunError(this.piMessageError(s,s.errorMessage)),this.scheduleDoneGuard());break}case"tool_execution_start":{if(this.cancelDoneGuard(),this.flushTextBuffer(),!this.activeEventId||!this.activeSessionId)break;const s=t;if(s.toolName){const i=L(s.args);this.callbacks.sendToolUse(this.activeEventId,this.activeSessionId,s.toolName,i)}break}case"tool_execution_end":{if(!this.activeEventId||!this.activeSessionId)break;const s=t;s.isError&&this.callbacks.sendToolResult(this.activeEventId,this.activeSessionId,s.toolName,"(failed)");break}case"agent_end":{a.info("pi-adapter",`agent_end event=${this.activeEventId} sessionId=${this.activeSessionId}`),this.cancelDoneGuard(),this.flushTextBuffer(),this.isStreaming=!1,this.stopComposing(),this.stopIdleTimer(),this.completeActiveEvent();break}case"agent_start":{this.cancelDoneGuard(),this.activeEventId&&(a.info("pi-adapter",`agent_start event=${this.activeEventId}`),this.isStreaming=!0);break}default:break}}appendText(t){if(this.textBuffer+=t,this.textBuffer.length>=et){this.flushTextBuffer();return}this.scheduleTextFlush()}scheduleTextFlush(){this.textFlushTimer||(this.textFlushTimer=setTimeout(()=>{this.textFlushTimer=null,this.flushTextBuffer()},tt))}flushTextBuffer(){this.stopTextFlush(),!(!this.textBuffer||!this.activeEventId||!this.activeSessionId)&&(this.sendTextChunks(this.textBuffer),this.textBuffer="")}sendTextChunks(t){if(!(!this.activeEventId||!this.activeSessionId))for(const e of z(t))this.callbacks.sendStreamChunk(this.activeEventId,this.activeSessionId,e,this.nextStreamSeq(),!1,this.activeClientMsgId??void 0)}stopTextFlush(){this.textFlushTimer&&(clearTimeout(this.textFlushTimer),this.textFlushTimer=null)}buildPromptText(t){let e=t.content||"";if(t.context_messages_json)try{const s=JSON.parse(t.context_messages_json);Array.isArray(s)&&s.length>0&&(e=s.map(n=>`[context] ${n.sender_id??"unknown"}: ${n.content}`).join(`
3
+ `)}`,data:r}:{status:"ok",message:"No models available",data:[]}}case"interrupt":return this.isStreaming?(await this.transport.send("abort"),this.activeEventId&&this.callbacks.sendEventResult(this.activeEventId,"canceled","interrupted"),this.clearActive(),{status:"ok",message:"Run interrupted"}):{status:"failed",message:"No active run to interrupt"};case"status":{const i=this.getStatus();return{status:"ok",message:`Alive: ${i.alive}, Busy: ${i.busy}, Session: ${this.piSessionPath??"none"}`,data:{alive:i.alive,busy:i.busy,sessions:i.sessions,sessionPath:this.piSessionPath}}}case"skills":{const i=U({mode:"pi"}),n=i.map(r=>`- ${r.name}${r.trigger?` (${r.trigger})`:""} [${r.source}]: ${r.description}`);return{status:"ok",message:n.length>0?n.join(`
4
+ `):"No skills found",data:i}}default:return{status:"unsupported",message:`Unknown command: ${t}`}}}catch(i){return{status:"failed",message:i instanceof Error?i.message:String(i)}}}resolveCwd(){if(this.bindingStore&&this.aibotSessionId){const e=this.bindingStore.get(this.aibotSessionId);if(e?.cwd)return e.cwd}const t=(this.config.options??{}).cwd;return typeof t=="string"&&t?t:process.cwd()}async startInternalApi(){try{this.internalApi=new G,this.internalApi.setInvokeHandler(async(r,c,l,h)=>this.callbacks.agentInvoke(r,c,h)),await this.internalApi.start(0),a.info("pi-adapter",`Internal API started at ${this.internalApi.url}`);const t=this.getMcpConfig(),e=X(),s=u(F(),"grix-pi-mcp");T(s,{recursive:!0}),this.mcpConfigPath=u(s,`mcp-${process.pid}-${Date.now()}.json`),$(this.mcpConfigPath,JSON.stringify({mcpServers:{...e,[t.name]:{command:t.command,args:t.args,directTools:!0,lifecycle:"eager"}}}),"utf8"),a.info("pi-adapter",`MCP config written to ${this.mcpConfigPath}`);const i=u(f(),".pi","agent","skills"),n=j(i);n.length>0&&a.info("pi-adapter",`Synced connector skills to ${i}: [${n.join(", ")}]`)}catch(t){a.warn("pi-adapter",`Failed to start MCP tools (non-fatal): ${t instanceof Error?t.message:String(t)}`)}}async spawnPi(){const t=this.config.command||"pi",e=B(t,typeof process.env.PATH=="string"?process.env.PATH:void 0),s=this.config.args??[],n=s.some(o=>o.startsWith("--mode"))?[...s]:["--mode","rpc",...s],r=String(this.config.env?.PI_CODING_AGENT_DIR??"").trim()||u(f(),".pi","agent"),c=Y(this.agentName);this.provider?await V(r,c,this.provider)?(n.includes("--provider")||n.push("--provider",c),!n.includes("--model")&&this.provider.model?.trim()&&n.push("--model",this.provider.model.trim())):a.warn("pi-adapter","provider config incomplete (baseUrl/model required); Grix provider not applied"):await J(r,c).then(o=>{o&&a.info("pi-adapter",`Removed stale Grix provider "${c}" from models.json`)}).catch(o=>{a.warn("pi-adapter",`Failed to clean up Grix provider config: ${o instanceof Error?o.message:String(o)}`)}),this.mcpConfigPath&&!n.some(o=>o==="--mcp-config")&&(await K(e)?n.push("--mcp-config",this.mcpConfigPath):a.warn("pi-adapter","\u5F53\u524D Pi \u4E0D\u652F\u6301 --mcp-config\uFF08\u672A\u68C0\u6D4B\u5230 pi-mcp-adapter \u6269\u5C55\uFF09\uFF0C\u8DF3\u8FC7 MCP \u6CE8\u5165\u4EE5\u907F\u514D\u542F\u52A8\u5931\u8D25"));const l={...process.env,...this.config.env},h=this.resolveCwd();a.info("pi-adapter",`Spawning: ${e} ${n.join(" ")}`);try{if(!(await b(h)).isDirectory())throw new Error(`Bound path is not a directory: ${h}`)}catch(o){throw String(o?.code??"")==="ENOENT"?new Error(`Bound directory does not exist: ${h}. Please rebind with /grix open <valid-directory>.`):o}try{this.process=D(e,n,{env:l,cwd:h}).process}catch(o){throw a.error("pi-adapter",`PI spawn threw: ${o}`),this.alive=!1,o}this.process.on("error",o=>{a.error("pi-adapter",`Spawn error: ${o.message}`),this.alive=!1,this.transport.close(),this.activeEventId&&(this.callbacks.sendEventResult(this.activeEventId,"failed",`Spawn error: ${o.message}`),this.clearActive()),this.stopped||this.emit("exit",1)}),this.process.on("exit",o=>{a.info("pi-adapter",`PI process exited (code=${o})`),this.alive=!1,this.transport.close(),this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.activeEventId&&this.callbacks.sendEventResult(this.activeEventId,"failed",`PI process exited (code=${o})`),this.isStreaming=!1,this.clearActive(),this.stopped||this.emit("exit",o??1)}),this.process.stderr?.on("data",o=>{const v=o.toString().trim();v&&a.info("pi-adapter",`[pi stderr] ${v}`)}),this.transport.on("event",o=>this.handlePiEvent(o)),this.transport.bind(this.process.stdin,this.process.stdout),this.alive=!0}handlePiEvent(t){if(this.stopped)return;if(this.lastRpcAt=Date.now(),t.type==="tool_execution_start"){const s=t;s.toolCallId&&this.toolCallsInFlight.add(s.toolCallId)}else if(t.type==="tool_execution_end"){const s=t;s.toolCallId&&this.toolCallsInFlight.delete(s.toolCallId)}switch(this.resetIdleTimer(),t.type){case"message_update":{const s=t.assistantMessageEvent;if(!s)break;const i=s.type;if(i==="text_delta"){const n=s.delta;if(n){const r=typeof s.contentIndex=="number"?s.contentIndex:0;this.rememberEmittedText(r,n),this.appendText(n)}}else if(i==="text_end"){const n=s.content,r=typeof s.contentIndex=="number"?s.contentIndex:0;n&&this.appendMissingText(r,n)}else if(i==="thinking_delta"){const n=s.delta;n&&this.activeEventId&&this.activeSessionId&&(this.thinkingSeq++,this.callbacks.sendThinking(this.activeEventId,this.activeSessionId,n))}else if(i==="done"||i==="error"){if(this.flushTextBuffer(),i==="error"){const n=this.piMessageError(s.error,s.reason);a.warn("pi-adapter",`terminal message_update error event=${this.activeEventId} sessionId=${this.activeSessionId} error=${p(s.error)} reason=${p(s.reason)}`),this.recordRunError(n)}this.scheduleDoneGuard()}break}case"message_end":{const s=t.message;s?.role==="assistant"&&s.stopReason==="error"&&(a.warn("pi-adapter",`terminal message_end error event=${this.activeEventId} sessionId=${this.activeSessionId} errorMessage=${p(s.errorMessage)} message=${p(s)}`),this.recordRunError(this.piMessageError(s,s.errorMessage)),this.scheduleDoneGuard());break}case"tool_execution_start":{if(this.cancelDoneGuard(),this.flushTextBuffer(),!this.activeEventId||!this.activeSessionId)break;const s=t;if(s.toolName){const i=O(s.args);this.callbacks.sendToolUse(this.activeEventId,this.activeSessionId,s.toolName,i)}break}case"tool_execution_end":{if(!this.activeEventId||!this.activeSessionId)break;const s=t;s.isError&&this.callbacks.sendToolResult(this.activeEventId,this.activeSessionId,s.toolName,"(failed)");break}case"agent_end":{a.info("pi-adapter",`agent_end event=${this.activeEventId} sessionId=${this.activeSessionId}`),this.cancelDoneGuard(),this.flushTextBuffer(),this.isStreaming=!1,this.stopComposing(),this.stopIdleTimer(),this.completeActiveEvent();break}case"agent_start":{this.cancelDoneGuard(),this.activeEventId&&(a.info("pi-adapter",`agent_start event=${this.activeEventId}`),this.isStreaming=!0);break}default:break}}appendText(t){if(this.textBuffer+=t,this.textBuffer.length>=st){this.flushTextBuffer();return}this.scheduleTextFlush()}scheduleTextFlush(){this.textFlushTimer||(this.textFlushTimer=setTimeout(()=>{this.textFlushTimer=null,this.flushTextBuffer()},et))}flushTextBuffer(){this.stopTextFlush(),!(!this.textBuffer||!this.activeEventId||!this.activeSessionId)&&(this.sendTextChunks(this.textBuffer),this.textBuffer="")}sendTextChunks(t){if(!(!this.activeEventId||!this.activeSessionId))for(const e of H(t))this.callbacks.sendStreamChunk(this.activeEventId,this.activeSessionId,e,this.nextStreamSeq(),!1,this.activeClientMsgId??void 0)}stopTextFlush(){this.textFlushTimer&&(clearTimeout(this.textFlushTimer),this.textFlushTimer=null)}buildPromptText(t){let e=t.content||"";if(t.context_messages_json)try{const s=JSON.parse(t.context_messages_json);Array.isArray(s)&&s.length>0&&(e=s.map(n=>`[context] ${n.sender_id??"unknown"}: ${n.content}`).join(`
5
5
  `)+`
6
6
 
7
7
  `+e)}catch{}return this.identity.injectOnce(t.session_id,e)}buildPromptTextFromRequest(t){let e=t.text;return t.contextMessages&&t.contextMessages.length>0&&(e=t.contextMessages.map(i=>`[context] ${i.senderId}: ${i.content}`).join(`
8
8
  `)+`
9
9
 
10
- `+e),this.identity.injectOnce(t.adapterSessionId,e)}clearActive(){const t=this.activeEventId;t&&this.snapshotAuditBoundary(t),this.activeEventId=null,this.activeSessionId=null,this.activeClientMsgId=null,this.activeRunError=null,this.textBuffer="",this.emittedTextByIndex.clear(),this.toolCallsInFlight.clear(),this.livenessExtendStartAt=void 0,this.stopIdleTimer(),t&&this.emit("eventDone",t)}beginAuditCapture(t){if(this.activeAuditCapture?.eventId===t.event_id||(this.activeAuditCapture=null,t.audit?.enabled!==!0))return;const e=this.piSessionPath??void 0;let s;if(e)try{s=u(e).size}catch{s=0}this.activeAuditCapture={eventId:t.event_id,...e?{transcriptPath:e}:{},...s===void 0?{}:{transcriptStartOffset:s}}}snapshotAuditBoundary(t){const e=this.activeAuditCapture;if(!e||e.eventId!==t)return;this.activeAuditCapture=null;const s={adapterType:"pi"};if(e.transcriptPath){let i=e.transcriptStartOffset??0;try{i=u(e.transcriptPath).size}catch{}s.transcriptPath=e.transcriptPath,s.transcriptStartOffset=e.transcriptStartOffset??0,s.transcriptEndOffset=i,s.providerSessionId=this.piSessionPath??e.transcriptPath}this.completedAuditBoundaries.set(t,s)}resetRunStreamState(){this.streamSeq=0,this.thinkingSeq=0,this.textBuffer="",this.activeRunError=null,this.emittedTextByIndex.clear(),this.activeClientMsgId=`pi_${++this.clientMsgSeq}_${Date.now()}`}nextStreamSeq(){return this.streamSeq++,this.streamSeq}completeActiveEvent(){if(!this.activeEventId){this.clearActive();return}const t=this.activeEventId,e=this.activeSessionId??"",s=this.activeClientMsgId??void 0,i=this.activeRunError??void 0;this.clearActive(),this.finalizeEvent(t,e,s,i)}finalizeEvent(t,e,s,i){const n=this.nextStreamSeq(),r=i?"failed":"responded";this.callbacks.sendFinalStreamChunkReliable?this.callbacks.sendFinalStreamChunkReliable(t,e,s).then(()=>{this.callbacks.sendEventResult(t,r,i),a.info("pi-adapter",`event ${r} (reliable) event=${t}`)}).catch(d=>{a.error("pi-adapter",`finalStreamChunk ACK failed event=${t}: ${d}`),this.callbacks.sendEventResult(t,"failed",`final output delivery failed: ${d instanceof Error?d.message:String(d)}`),a.info("pi-adapter",`event failed (final delivery) event=${t}`)}):(this.callbacks.sendStreamChunk(t,e,"",n,!0,s),this.callbacks.sendEventResult(t,r,i),a.info("pi-adapter",`event ${r} event=${t}`))}recordRunError(t){if(this.activeRunError||!this.activeEventId||!this.activeSessionId)return;const e=t.trim()||"Pi request failed";this.activeRunError=e,this.flushTextBuffer(),this.callbacks.sendRunError(this.activeEventId,this.activeSessionId,e,this.nextStreamSeq(),this.activeClientMsgId??void 0)}piMessageError(t,e){if(t&&typeof t=="object"){const s=t.errorMessage;if(typeof s=="string"&&s.trim())return s.trim()}return typeof t=="string"&&t.trim()?t.trim():typeof e=="string"&&e.trim()&&e.trim()!=="error"?e.trim():"Pi request failed"}rememberEmittedText(t,e){this.emittedTextByIndex.set(t,(this.emittedTextByIndex.get(t)??"")+e)}appendMissingText(t,e){const s=this.emittedTextByIndex.get(t)??"";if(e!==s){if(e.startsWith(s)){const i=e.slice(s.length);i&&(this.rememberEmittedText(t,i),this.appendText(i));return}if(!s){this.rememberEmittedText(t,e),this.appendText(e);return}a.info("pi-adapter",`text_end content mismatch at index=${t}, keeping streamed deltas`)}}startComposing(t,e){this.stopComposing(),this.composingSessionId=t,this.callbacks.sendSessionActivitySet(t,"composing",!0,{ref_event_id:e,ttl_ms:12e4}),this.composingInterval=setInterval(()=>{this.composingSessionId&&this.callbacks.sendSessionActivitySet(this.composingSessionId,"composing",!0,{ttl_ms:12e4})},3e4)}stopComposing(){this.composingInterval&&(clearInterval(this.composingInterval),this.composingInterval=null),this.composingSessionId&&(this.callbacks.sendSessionActivitySet(this.composingSessionId,"composing",!1),this.composingSessionId=null)}static DONE_GUARD_MS=5e3;scheduleDoneGuard(){this.cancelDoneGuard(),!(!this.activeEventId||!this.activeSessionId)&&(this.doneGuardTimer=setTimeout(()=>{this.doneGuardTimer=null,!(!this.activeEventId||!this.isStreaming)&&(a.info("pi-adapter",`done guard triggered \u2014 no agent_end received, ending event=${this.activeEventId}`),this.flushTextBuffer(),this.isStreaming=!1,this.stopComposing(),this.stopIdleTimer(),this.completeActiveEvent())},y.DONE_GUARD_MS))}cancelDoneGuard(){this.doneGuardTimer&&(clearTimeout(this.doneGuardTimer),this.doneGuardTimer=null)}shouldExtendByLiveness(){if(!this.isStreaming||!this.activeEventId)return!1;const t=this.process?.pid;if(!this.alive||!t||!E(t))return!1;const e=Date.now(),s=this.lastRpcAt>0?e-this.lastRpcAt:Number.POSITIVE_INFINITY;if(s<Z)return this.livenessExtendStartAt=e,a.info("pi-adapter",`Liveness check: fresh RPC for ${this.activeEventId} (rpcAge=${s}ms, toolsInFlight=${this.toolCallsInFlight.size}), extending`),!0;const n=this.livenessExtendStartAt??e;return this.livenessExtendStartAt===void 0&&(this.livenessExtendStartAt=n),e-n>x?(a.warn("pi-adapter",`Liveness extension budget exhausted for ${this.activeEventId} (no progress for ${Math.round((e-n)/6e4)}min), allowing idle fail`),!1):(a.info("pi-adapter",`Liveness check: turn in progress for ${this.activeEventId} (toolsInFlight=${this.toolCallsInFlight.size}, rpcAge=${Number.isFinite(s)?`${s}ms`:"n/a"}), extending`),!0)}resetIdleTimer(){this.stopIdleTimer(),!(this.stopped||!this.isStreaming)&&(this.idleTimer=setTimeout(()=>{if(this.stopped||!this.isStreaming)return;if(this.shouldExtendByLiveness()){this.resetIdleTimer();return}const t=this.process?.pid,s=!!(this.alive&&t&&E(t))?`liveness budget exhausted after ${x/6e4}min`:"process not alive";a.error("pi-adapter",`Idle timeout (${s}, toolsInFlight=${this.toolCallsInFlight.size}) \u2014 emitting exit for respawn`),this.flushTextBuffer(),this.isStreaming=!1,this.stopComposing(),this.activeEventId&&this.callbacks.sendEventResult(this.activeEventId,"failed",`idle timeout: ${s}`),this.clearActive(),this.emit("exit",-1)},Q))}stopIdleTimer(){this.idleTimer&&(clearTimeout(this.idleTimer),this.idleTimer=null)}async ensureSessionReady(){return this.sessionReadyPromise?this.sessionReadyPromise:(this.sessionReadyPromise=this.restoreOrCreateSession().finally(()=>{this.sessionReadyPromise=null}),this.sessionReadyPromise)}async restoreOrCreateSession(){try{await this.switchSession();return}catch(t){this.piSessionPath&&a.error("pi-adapter",`switch_session failed, creating new session: ${t}`)}await this.createNewSession()}async switchSession(){if(!this.piSessionPath)throw new Error("no PI session path");const t=await this.transport.send("switch_session",{sessionPath:this.piSessionPath});if(!t.success)throw this.piSessionPath=null,this.persistPiSessionPath(void 0),new Error(`switch_session failed: ${t.error}`)}async createNewSession(){const t=await this.transport.send("new_session");if(!t.success)throw new Error(`new_session failed: ${t.error}`);let e=null;if(e=t.data?.sessionFile??null,!e)try{const i=await this.transport.send("get_state");i.success&&(e=i.data?.sessionFile??null)}catch{}this.piSessionPath=e,this.persistPiSessionPath(this.piSessionPath??void 0)}persistPiSessionPath(t){!this.bindingStore||!this.aibotSessionId||this.bindingStore.setPiSessionPath(this.aibotSessionId,t)}}export{y as PiAdapter};
10
+ `+e),this.identity.injectOnce(t.adapterSessionId,e)}clearActive(){const t=this.activeEventId;t&&this.snapshotAuditBoundary(t),this.activeEventId=null,this.activeSessionId=null,this.activeClientMsgId=null,this.activeRunError=null,this.textBuffer="",this.emittedTextByIndex.clear(),this.toolCallsInFlight.clear(),this.livenessExtendStartAt=void 0,this.stopIdleTimer(),t&&this.emit("eventDone",t)}beginAuditCapture(t){if(this.activeAuditCapture?.eventId===t.event_id||(this.activeAuditCapture=null,t.audit?.enabled!==!0))return;const e=this.piSessionPath??void 0;let s;if(e)try{s=m(e).size}catch{s=0}this.activeAuditCapture={eventId:t.event_id,...e?{transcriptPath:e}:{},...s===void 0?{}:{transcriptStartOffset:s}}}snapshotAuditBoundary(t){const e=this.activeAuditCapture;if(!e||e.eventId!==t)return;this.activeAuditCapture=null;const s={adapterType:"pi"};if(e.transcriptPath){let i=e.transcriptStartOffset??0;try{i=m(e.transcriptPath).size}catch{}s.transcriptPath=e.transcriptPath,s.transcriptStartOffset=e.transcriptStartOffset??0,s.transcriptEndOffset=i,s.providerSessionId=this.piSessionPath??e.transcriptPath}this.completedAuditBoundaries.set(t,s)}resetRunStreamState(){this.streamSeq=0,this.thinkingSeq=0,this.textBuffer="",this.activeRunError=null,this.emittedTextByIndex.clear(),this.activeClientMsgId=`pi_${++this.clientMsgSeq}_${Date.now()}`}nextStreamSeq(){return this.streamSeq++,this.streamSeq}completeActiveEvent(){if(!this.activeEventId){this.clearActive();return}const t=this.activeEventId,e=this.activeSessionId??"",s=this.activeClientMsgId??void 0,i=this.activeRunError??void 0;this.clearActive(),this.finalizeEvent(t,e,s,i)}finalizeEvent(t,e,s,i){const n=this.nextStreamSeq(),r=i?"failed":"responded";a.info("pi-adapter",`finalize event=${t} sessionId=${e} status=${r} clientMsgId=${s??""} terminalError=${i?p(i):""}`),this.callbacks.sendFinalStreamChunkReliable?this.callbacks.sendFinalStreamChunkReliable(t,e,s).then(()=>{this.callbacks.sendEventResult(t,r,i),a.info("pi-adapter",`event ${r} (reliable) event=${t}`)}).catch(c=>{a.error("pi-adapter",`finalStreamChunk ACK failed event=${t}: ${c}`),this.callbacks.sendEventResult(t,"failed",`final output delivery failed: ${c instanceof Error?c.message:String(c)}`),a.info("pi-adapter",`event failed (final delivery) event=${t}`)}):(this.callbacks.sendStreamChunk(t,e,"",n,!0,s),this.callbacks.sendEventResult(t,r,i),a.info("pi-adapter",`event ${r} event=${t}`))}recordRunError(t){if(this.activeRunError||!this.activeEventId||!this.activeSessionId)return;const e=t.trim()||"Pi request failed";this.activeRunError=e,a.warn("pi-adapter",`recorded terminal error event=${this.activeEventId} sessionId=${this.activeSessionId} error=${p(e)}`),this.flushTextBuffer(),this.callbacks.sendRunError(this.activeEventId,this.activeSessionId,e,this.nextStreamSeq(),this.activeClientMsgId??void 0)}piMessageError(t,e){if(t&&typeof t=="object"){const s=t.errorMessage;if(typeof s=="string"&&s.trim())return s.trim()}return typeof t=="string"&&t.trim()?t.trim():typeof e=="string"&&e.trim()&&e.trim()!=="error"?e.trim():"Pi request failed"}rememberEmittedText(t,e){this.emittedTextByIndex.set(t,(this.emittedTextByIndex.get(t)??"")+e)}appendMissingText(t,e){const s=this.emittedTextByIndex.get(t)??"";if(e!==s){if(e.startsWith(s)){const i=e.slice(s.length);i&&(this.rememberEmittedText(t,i),this.appendText(i));return}if(!s){this.rememberEmittedText(t,e),this.appendText(e);return}a.info("pi-adapter",`text_end content mismatch at index=${t}, keeping streamed deltas`)}}startComposing(t,e){this.stopComposing(),this.composingSessionId=t,this.callbacks.sendSessionActivitySet(t,"composing",!0,{ref_event_id:e,ttl_ms:12e4}),this.composingInterval=setInterval(()=>{this.composingSessionId&&this.callbacks.sendSessionActivitySet(this.composingSessionId,"composing",!0,{ttl_ms:12e4})},3e4)}stopComposing(){this.composingInterval&&(clearInterval(this.composingInterval),this.composingInterval=null),this.composingSessionId&&(this.callbacks.sendSessionActivitySet(this.composingSessionId,"composing",!1),this.composingSessionId=null)}static DONE_GUARD_MS=5e3;scheduleDoneGuard(){this.cancelDoneGuard(),!(!this.activeEventId||!this.activeSessionId)&&(this.doneGuardTimer=setTimeout(()=>{this.doneGuardTimer=null,!(!this.activeEventId||!this.isStreaming)&&(a.info("pi-adapter",`done guard triggered \u2014 no agent_end received, ending event=${this.activeEventId}`),this.flushTextBuffer(),this.isStreaming=!1,this.stopComposing(),this.stopIdleTimer(),this.completeActiveEvent())},_.DONE_GUARD_MS))}cancelDoneGuard(){this.doneGuardTimer&&(clearTimeout(this.doneGuardTimer),this.doneGuardTimer=null)}shouldExtendByLiveness(){if(!this.isStreaming||!this.activeEventId)return!1;const t=this.process?.pid;if(!this.alive||!t||!y(t))return!1;const e=Date.now(),s=this.lastRpcAt>0?e-this.lastRpcAt:Number.POSITIVE_INFINITY;if(s<tt)return this.livenessExtendStartAt=e,a.info("pi-adapter",`Liveness check: fresh RPC for ${this.activeEventId} (rpcAge=${s}ms, toolsInFlight=${this.toolCallsInFlight.size}), extending`),!0;const n=this.livenessExtendStartAt??e;return this.livenessExtendStartAt===void 0&&(this.livenessExtendStartAt=n),e-n>E?(a.warn("pi-adapter",`Liveness extension budget exhausted for ${this.activeEventId} (no progress for ${Math.round((e-n)/6e4)}min), allowing idle fail`),!1):(a.info("pi-adapter",`Liveness check: turn in progress for ${this.activeEventId} (toolsInFlight=${this.toolCallsInFlight.size}, rpcAge=${Number.isFinite(s)?`${s}ms`:"n/a"}), extending`),!0)}resetIdleTimer(){this.stopIdleTimer(),!(this.stopped||!this.isStreaming)&&(this.idleTimer=setTimeout(()=>{if(this.stopped||!this.isStreaming)return;if(this.shouldExtendByLiveness()){this.resetIdleTimer();return}const t=this.process?.pid,s=!!(this.alive&&t&&y(t))?`liveness budget exhausted after ${E/6e4}min`:"process not alive";a.error("pi-adapter",`Idle timeout (${s}, toolsInFlight=${this.toolCallsInFlight.size}) \u2014 emitting exit for respawn`),this.flushTextBuffer(),this.isStreaming=!1,this.stopComposing(),this.activeEventId&&this.callbacks.sendEventResult(this.activeEventId,"failed",`idle timeout: ${s}`),this.clearActive(),this.emit("exit",-1)},Z))}stopIdleTimer(){this.idleTimer&&(clearTimeout(this.idleTimer),this.idleTimer=null)}async ensureSessionReady(){return this.sessionReadyPromise?this.sessionReadyPromise:(this.sessionReadyPromise=this.restoreOrCreateSession().finally(()=>{this.sessionReadyPromise=null}),this.sessionReadyPromise)}async restoreOrCreateSession(){try{await this.switchSession();return}catch(t){this.piSessionPath&&a.error("pi-adapter",`switch_session failed, creating new session: ${t}`)}await this.createNewSession()}async switchSession(){if(!this.piSessionPath)throw new Error("no PI session path");const t=await this.transport.send("switch_session",{sessionPath:this.piSessionPath});if(!t.success)throw this.piSessionPath=null,this.persistPiSessionPath(void 0),new Error(`switch_session failed: ${t.error}`)}async createNewSession(){const t=await this.transport.send("new_session");if(!t.success)throw new Error(`new_session failed: ${t.error}`);let e=null;if(e=t.data?.sessionFile??null,!e)try{const i=await this.transport.send("get_state");i.success&&(e=i.data?.sessionFile??null)}catch{}this.piSessionPath=e,this.persistPiSessionPath(this.piSessionPath??void 0)}persistPiSessionPath(t){!this.bindingStore||!this.aibotSessionId||this.bindingStore.setPiSessionPath(this.aibotSessionId,t)}}export{_ as PiAdapter};
@@ -1,3 +1,3 @@
1
- import{mkdir as m,readFile as y,writeFile as u}from"node:fs/promises";import{join as d}from"node:path";function o(r){return r&&typeof r=="object"&&!Array.isArray(r)?r:{}}async function f(r){try{return o(JSON.parse(await y(r,"utf8")))}catch{return{}}}const p=new Map;function l(r,e){const t=(p.get(r)??Promise.resolve()).then(e,e);return p.set(r,t.catch(()=>{})),t}function x(r){const e=r.trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/^-|-$/g,"");return e?`grix-${e}`:"grix"}async function g(r,e,t){const i=t.baseUrl?.trim(),n=t.model?.trim();if(!i||!n)return!1;const s=d(r,"models.json");return l(s,async()=>{const a=await f(s),c=o(a.providers);return c[e]={baseUrl:i,api:"openai-completions",apiKey:"GRIX_PROVIDER_API_KEY",models:[{id:n,name:n}]},a.providers=c,await m(r,{recursive:!0}),await u(s,`${JSON.stringify(a,null,2)}
2
- `,{encoding:"utf8",mode:384}),!0})}async function O(r,e){const t=d(r,"models.json");return l(t,async()=>{const i=await f(t),n=o(i.providers);return o(n[e]).apiKey!=="GRIX_PROVIDER_API_KEY"?!1:(delete n[e],i.providers=n,await u(t,`${JSON.stringify(i,null,2)}
3
- `,{encoding:"utf8",mode:384}),!0)})}export{x as piGrixProviderId,O as removePiGrixProvider,g as writePiGrixProvider};
1
+ import{mkdir as P,readFile as _,writeFile as d}from"node:fs/promises";import{join as u}from"node:path";import{log as x}from"../../core/log/index.js";const c="GRIX_PROVIDER_API_KEY",l=`$${c}`;function s(r){return r&&typeof r=="object"&&!Array.isArray(r)?r:{}}async function f(r){try{return s(JSON.parse(await _(r,"utf8")))}catch{return{}}}const m=new Map;function y(r,e){const t=(m.get(r)??Promise.resolve()).then(e,e);return m.set(r,t.catch(()=>{})),t}function w(r){const e=r.trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/^-|-$/g,"");return e?`grix-${e}`:"grix"}async function I(r,e,t){const o=t.baseUrl?.trim(),i=t.model?.trim();if(!o||!i)return!1;const n=u(r,"models.json");return y(n,async()=>{const a=await f(n),p=s(a.providers);return p[e]={baseUrl:o,api:"openai-completions",apiKey:l,models:[{id:i,name:i}]},a.providers=p,await P(r,{recursive:!0}),await d(n,`${JSON.stringify(a,null,2)}
2
+ `,{encoding:"utf8",mode:384}),x.info("pi-provider",`Wrote Grix provider providerId=${e} baseUrl=${o} model=${i} api=openai-completions apiKeyEnv=${c} path=${n}`),!0})}async function E(r,e){const t=u(r,"models.json");return y(t,async()=>{const o=await f(t),i=s(o.providers),n=s(i[e]).apiKey;return n!==l&&n!==c?!1:(delete i[e],o.providers=i,await d(t,`${JSON.stringify(o,null,2)}
3
+ `,{encoding:"utf8",mode:384}),!0)})}export{w as piGrixProviderId,E as removePiGrixProvider,I as writePiGrixProvider};
@@ -1 +1 @@
1
- const s={qwen:{baseUrl:"OPENAI_BASE_URL",apiKey:"OPENAI_API_KEY",model:"OPENAI_MODEL"},reasonix:{baseUrl:"DEEPSEEK_BASE_URL",apiKey:"DEEPSEEK_API_KEY"},codewhale:{apiKey:"OPENAI_API_KEY"},hermes:{baseUrl:"GRIX_HERMES_BASE_URL",apiKey:"GRIX_PROVIDER_API_KEY",model:"HERMES_INFERENCE_MODEL",fixed:{HERMES_INFERENCE_PROVIDER:"grix"}},pi:{apiKey:"GRIX_PROVIDER_API_KEY"}},_=["qwen","reasonix","codewhale","hermes","pi","opencode"],a=["kiro"];function R(e){const r=e?.trim().toLowerCase();return!!r&&_.includes(r)}function p(e){const r=e?.trim().toLowerCase();return!!r&&a.includes(r)}function I(e){return R(e)||p(e)}function P(e){return!!e&&e.trim().toLowerCase()in s}function c(e,r){if(!r)return{};const o=s[e?.trim().toLowerCase()??""];if(!o)return{};const t=r.baseUrl?.trim(),i=r.apiKey?.trim(),n=r.model?.trim();if(!t&&!i&&!n)return{};const E={...o.fixed??{}};return o.baseUrl&&t&&(E[o.baseUrl]=t),o.apiKey&&i&&(E[o.apiKey]=i),o.model&&n&&(E[o.model]=n),E}export{_ as DIRECT_PROVIDER_CLIENT_TYPES,a as KIRO_CW_RELAY_CLIENT_TYPES,s as PROVIDER_ENV_SPECS,c as buildProviderEnv,R as supportsDirectProviderConfig,p as supportsKiroCwRelay,I as supportsNonMitmRelayConfig,P as supportsProviderEnv};
1
+ const n={qwen:{baseUrl:"OPENAI_BASE_URL",apiKey:"OPENAI_API_KEY",model:"OPENAI_MODEL"},kimi:{baseUrl:"KIMI_MODEL_BASE_URL",apiKey:"KIMI_MODEL_API_KEY",model:"KIMI_MODEL_NAME",fixed:{KIMI_MODEL_PROVIDER_TYPE:"openai",KIMI_MODEL_CAPABILITIES:"tool_use",KIMI_MODEL_MAX_CONTEXT_SIZE:"262144"}},reasonix:{baseUrl:"DEEPSEEK_BASE_URL",apiKey:"DEEPSEEK_API_KEY"},codewhale:{apiKey:"OPENAI_API_KEY"},hermes:{baseUrl:"GRIX_HERMES_BASE_URL",apiKey:"GRIX_PROVIDER_API_KEY",model:"HERMES_INFERENCE_MODEL",fixed:{HERMES_INFERENCE_PROVIDER:"grix"}},pi:{apiKey:"GRIX_PROVIDER_API_KEY"}},I=["qwen","kimi","reasonix","codewhale","hermes","pi","opencode"],s=["kiro"];function a(e){const E=e?.trim().toLowerCase();return!!E&&I.includes(E)}function R(e){const E=e?.trim().toLowerCase();return!!E&&s.includes(E)}function p(e){return a(e)||R(e)}function K(e){return!!e&&e.trim().toLowerCase()in n}function P(e,E){if(!E)return{};const o=n[e?.trim().toLowerCase()??""];if(!o)return{};const i=E.baseUrl?.trim(),_=E.apiKey?.trim(),t=E.model?.trim();if(!i&&!_&&!t)return{};const r={...o.fixed??{}};return o.baseUrl&&i&&(r[o.baseUrl]=i),o.apiKey&&_&&(r[o.apiKey]=_),o.model&&t&&(r[o.model]=t),r}export{I as DIRECT_PROVIDER_CLIENT_TYPES,s as KIRO_CW_RELAY_CLIENT_TYPES,n as PROVIDER_ENV_SPECS,P as buildProviderEnv,a as supportsDirectProviderConfig,R as supportsKiroCwRelay,p as supportsNonMitmRelayConfig,K as supportsProviderEnv};
@@ -0,0 +1 @@
1
+ import{closeSync as t,fsyncSync as e,openSync as y}from"node:fs";function i(n){const c=y(n,"r");try{e(c)}catch(o){const r=o.code;if(process.platform!=="win32"||r!=="EPERM"&&r!=="EINVAL")throw o}finally{t(c)}}export{i as fsyncDirectory};
@@ -1,2 +1,2 @@
1
- import{chmodSync as g,closeSync as d,existsSync as _,fsyncSync as c,mkdirSync as k,openSync as l,readFileSync as w,renameSync as A,unlinkSync as u,writeFileSync as b}from"node:fs";import{randomUUID as v}from"node:crypto";import{dirname as p}from"node:path";const h=1;function m(o){return`${o.event_id?.trim()??""}\0${o.stop_id.trim()}`}function a(o){return{...o,payload:{...o.payload}}}function f(o){if(!o||typeof o!="object"||Array.isArray(o))return!1;const t=o;return!!(t.event_id?.trim()&&t.stop_id?.trim()&&t.terminal_commit_token?.trim()&&(t.status==="stopped"||t.status==="already_finished"))}class x{filePath;pending=new Map;constructor(t){this.filePath=t,this.load()}enqueue(t){if(!f(t))throw new Error("stop result outbox requires event_id, stop_id, terminal token, and valid status");const i={...t,event_id:t.event_id.trim(),stop_id:t.stop_id.trim(),terminal_commit_token:t.terminal_commit_token.trim()},n=m(i),r=this.pending.get(n);if(r){if(r.payload.status!==i.status||r.payload.terminal_commit_token!==i.terminal_commit_token)throw new Error(`stop result changed for event=${i.event_id} stop=${i.stop_id}`);return a(r)}const e=Date.now(),s={key:n,payload:i,generation:1,createdAt:e,updatedAt:e,attempts:0,nextAttemptAt:0};this.pending.set(n,s);try{this.persist()}catch(y){throw this.pending.delete(n),y}return a(s)}listPending(){return[...this.pending.values()].map(a)}isCurrent(t){const i=this.pending.get(t.key);return i?.generation===t.generation&&i.payload.status===t.payload.status&&i.payload.terminal_commit_token===t.payload.terminal_commit_token}acknowledge(t,i,n,r){if(i?.trim()!==t.payload.event_id||n?.trim()!==t.payload.terminal_commit_token||r!==!0||!this.isCurrent(t))return!1;const e=this.pending.get(t.key);this.pending.delete(t.key);try{this.persist()}catch(s){throw this.pending.set(t.key,e),s}return!0}recordRetry(t,i,n){const r=this.pending.get(t.key);if(!r||r.generation!==t.generation)return!1;const e=a(r);r.attempts+=1,r.updatedAt=Date.now(),r.nextAttemptAt=i,r.lastError=n;try{this.persist()}catch(s){throw this.pending.set(t.key,e),s}return!0}discard(t){const i=this.pending.get(t.key);if(!i||i.generation!==t.generation)return!1;this.pending.delete(t.key);try{this.persist()}catch(n){throw this.pending.set(t.key,i),n}return!0}load(){if(!this.filePath||!_(this.filePath))return;let t;try{t=JSON.parse(w(this.filePath,"utf8"))}catch(n){throw new Error(`stop result outbox load failed: ${n instanceof Error?n.message:n}`)}if(!t||typeof t!="object"||Array.isArray(t))throw new Error("stop result outbox load failed: invalid root");const i=t;if(i.version!==h||!i.pending||typeof i.pending!="object"||Array.isArray(i.pending))throw new Error("stop result outbox load failed: unsupported or invalid file");for(const[n,r]of Object.entries(i.pending)){if(!r||typeof r!="object")continue;const e=r;!f(e.payload)||n!==e.key||n!==m(e.payload)||this.pending.set(n,{key:n,payload:{...e.payload},generation:Number.isInteger(e.generation)&&e.generation>0?e.generation:1,createdAt:Number.isFinite(e.createdAt)?e.createdAt:Date.now(),updatedAt:Number.isFinite(e.updatedAt)?e.updatedAt:Date.now(),attempts:Number.isInteger(e.attempts)&&e.attempts>=0?e.attempts:0,nextAttemptAt:Number.isFinite(e.nextAttemptAt)?e.nextAttemptAt:0,...typeof e.lastError=="string"?{lastError:e.lastError}:{}})}}persist(){if(!this.filePath)return;if(this.pending.size===0){try{u(this.filePath);const r=l(p(this.filePath),"r");try{c(r)}finally{d(r)}}catch(r){if(r.code!=="ENOENT")throw r}return}const t=Object.create(null);for(const[r,e]of this.pending)t[r]=a(e);const i={version:h,pending:t};k(p(this.filePath),{recursive:!0,mode:448});const n=`${this.filePath}.${v()}.tmp`;try{b(n,`${JSON.stringify(i,null,2)}
2
- `,{encoding:"utf8",mode:384,flag:"wx"});const r=l(n,"r");try{c(r)}finally{d(r)}A(n,this.filePath);const e=l(p(this.filePath),"r");try{c(e)}finally{d(e)}try{g(this.filePath,384)}catch{}}finally{try{u(n)}catch{}}}}export{x as StopResultOutbox};
1
+ import{chmodSync as f,closeSync as y,existsSync as g,fsyncSync as _,mkdirSync as k,openSync as w,readFileSync as A,renameSync as b,unlinkSync as c,writeFileSync as v}from"node:fs";import{randomUUID as E}from"node:crypto";import{dirname as d}from"node:path";import{fsyncDirectory as p}from"./fsync.js";const l=1;function u(o){return`${o.event_id?.trim()??""}\0${o.stop_id.trim()}`}function a(o){return{...o,payload:{...o.payload}}}function m(o){if(!o||typeof o!="object"||Array.isArray(o))return!1;const t=o;return!!(t.event_id?.trim()&&t.stop_id?.trim()&&t.terminal_commit_token?.trim()&&(t.status==="stopped"||t.status==="already_finished"))}class ${filePath;pending=new Map;constructor(t){this.filePath=t,this.load()}enqueue(t){if(!m(t))throw new Error("stop result outbox requires event_id, stop_id, terminal token, and valid status");const r={...t,event_id:t.event_id.trim(),stop_id:t.stop_id.trim(),terminal_commit_token:t.terminal_commit_token.trim()},n=u(r),i=this.pending.get(n);if(i){if(i.payload.status!==r.status||i.payload.terminal_commit_token!==r.terminal_commit_token)throw new Error(`stop result changed for event=${r.event_id} stop=${r.stop_id}`);return a(i)}const e=Date.now(),s={key:n,payload:r,generation:1,createdAt:e,updatedAt:e,attempts:0,nextAttemptAt:0};this.pending.set(n,s);try{this.persist()}catch(h){throw this.pending.delete(n),h}return a(s)}listPending(){return[...this.pending.values()].map(a)}isCurrent(t){const r=this.pending.get(t.key);return r?.generation===t.generation&&r.payload.status===t.payload.status&&r.payload.terminal_commit_token===t.payload.terminal_commit_token}acknowledge(t,r,n,i){if(r?.trim()!==t.payload.event_id||n?.trim()!==t.payload.terminal_commit_token||i!==!0||!this.isCurrent(t))return!1;const e=this.pending.get(t.key);this.pending.delete(t.key);try{this.persist()}catch(s){throw this.pending.set(t.key,e),s}return!0}recordRetry(t,r,n){const i=this.pending.get(t.key);if(!i||i.generation!==t.generation)return!1;const e=a(i);i.attempts+=1,i.updatedAt=Date.now(),i.nextAttemptAt=r,i.lastError=n;try{this.persist()}catch(s){throw this.pending.set(t.key,e),s}return!0}discard(t){const r=this.pending.get(t.key);if(!r||r.generation!==t.generation)return!1;this.pending.delete(t.key);try{this.persist()}catch(n){throw this.pending.set(t.key,r),n}return!0}load(){if(!this.filePath||!g(this.filePath))return;let t;try{t=JSON.parse(A(this.filePath,"utf8"))}catch(n){throw new Error(`stop result outbox load failed: ${n instanceof Error?n.message:n}`)}if(!t||typeof t!="object"||Array.isArray(t))throw new Error("stop result outbox load failed: invalid root");const r=t;if(r.version!==l||!r.pending||typeof r.pending!="object"||Array.isArray(r.pending))throw new Error("stop result outbox load failed: unsupported or invalid file");for(const[n,i]of Object.entries(r.pending)){if(!i||typeof i!="object")continue;const e=i;!m(e.payload)||n!==e.key||n!==u(e.payload)||this.pending.set(n,{key:n,payload:{...e.payload},generation:Number.isInteger(e.generation)&&e.generation>0?e.generation:1,createdAt:Number.isFinite(e.createdAt)?e.createdAt:Date.now(),updatedAt:Number.isFinite(e.updatedAt)?e.updatedAt:Date.now(),attempts:Number.isInteger(e.attempts)&&e.attempts>=0?e.attempts:0,nextAttemptAt:Number.isFinite(e.nextAttemptAt)?e.nextAttemptAt:0,...typeof e.lastError=="string"?{lastError:e.lastError}:{}})}}persist(){if(!this.filePath)return;if(this.pending.size===0){try{c(this.filePath),p(d(this.filePath))}catch(i){if(i.code!=="ENOENT")throw i}return}const t=Object.create(null);for(const[i,e]of this.pending)t[i]=a(e);const r={version:l,pending:t};k(d(this.filePath),{recursive:!0,mode:448});const n=`${this.filePath}.${E()}.tmp`;try{v(n,`${JSON.stringify(r,null,2)}
2
+ `,{encoding:"utf8",mode:384,flag:"wx"});const i=w(n,"r+");try{_(i)}finally{y(i)}b(n,this.filePath),p(d(this.filePath));try{f(this.filePath,384)}catch{}}finally{try{c(n)}catch{}}}}export{$ as StopResultOutbox};
@@ -1,2 +1,2 @@
1
- import{chmodSync as m,closeSync as n,existsSync as d,fsyncSync as s,mkdirSync as y,openSync as a,readFileSync as k,renameSync as p,unlinkSync as h,writeFileSync as u}from"node:fs";import{randomUUID as w}from"node:crypto";import{dirname as c}from"node:path";const l=1;class v{filePath;tokens=new Map;constructor(r){this.filePath=r,this.load()}get(r){return this.tokens.get(r)}register(r,i){const t=r.trim(),e=i.trim();if(!t||!e)throw new Error("terminal commit token requires non-empty event_id and token");const o=this.tokens.get(t);if(o!==e){if(o)throw new Error(`terminal commit token changed for event=${t}`);this.tokens.set(t,e);try{this.persist()}catch(f){throw this.tokens.delete(t),f}}}remove(r,i){const t=this.tokens.get(r);if(!t||t!==i)return!1;this.tokens.delete(r);try{this.persist()}catch(e){throw this.tokens.set(r,t),e}return!0}load(){if(!this.filePath||!d(this.filePath))return;let r;try{r=JSON.parse(k(this.filePath,"utf8"))}catch(t){throw new Error(`terminal commit token store load failed: ${t instanceof Error?t.message:t}`)}if(!r||typeof r!="object"||Array.isArray(r))throw new Error("terminal commit token store load failed: invalid root");const i=r;if(i.version!==l||!i.tokens||typeof i.tokens!="object"||Array.isArray(i.tokens))throw new Error("terminal commit token store load failed: unsupported or invalid file");for(const[t,e]of Object.entries(i.tokens))t.trim()&&typeof e=="string"&&e.trim()&&this.tokens.set(t,e)}persist(){if(!this.filePath)return;if(this.tokens.size===0){try{h(this.filePath);const e=a(c(this.filePath),"r");try{s(e)}finally{n(e)}}catch(e){if(e.code!=="ENOENT")throw e}return}const r=Object.create(null);for(const[e,o]of this.tokens)r[e]=o;const i={version:l,tokens:r};y(c(this.filePath),{recursive:!0,mode:448});const t=`${this.filePath}.${w()}.tmp`;try{u(t,`${JSON.stringify(i,null,2)}
2
- `,{encoding:"utf8",mode:384,flag:"wx"});const e=a(t,"r");try{s(e)}finally{n(e)}p(t,this.filePath);const o=a(c(this.filePath),"r");try{s(o)}finally{n(o)}try{m(this.filePath,384)}catch{}}finally{try{h(t)}catch{}}}}export{v as TerminalCommitTokenStore};
1
+ import{chmodSync as f,closeSync as l,existsSync as m,fsyncSync as y,mkdirSync as d,openSync as k,readFileSync as p,renameSync as u,unlinkSync as s,writeFileSync as w}from"node:fs";import{randomUUID as P}from"node:crypto";import{dirname as n}from"node:path";import{fsyncDirectory as a}from"./fsync.js";const c=1;class N{filePath;tokens=new Map;constructor(e){this.filePath=e,this.load()}get(e){return this.tokens.get(e)}register(e,o){const t=e.trim(),r=o.trim();if(!t||!r)throw new Error("terminal commit token requires non-empty event_id and token");const i=this.tokens.get(t);if(i!==r){if(i)throw new Error(`terminal commit token changed for event=${t}`);this.tokens.set(t,r);try{this.persist()}catch(h){throw this.tokens.delete(t),h}}}remove(e,o){const t=this.tokens.get(e);if(!t||t!==o)return!1;this.tokens.delete(e);try{this.persist()}catch(r){throw this.tokens.set(e,t),r}return!0}load(){if(!this.filePath||!m(this.filePath))return;let e;try{e=JSON.parse(p(this.filePath,"utf8"))}catch(t){throw new Error(`terminal commit token store load failed: ${t instanceof Error?t.message:t}`)}if(!e||typeof e!="object"||Array.isArray(e))throw new Error("terminal commit token store load failed: invalid root");const o=e;if(o.version!==c||!o.tokens||typeof o.tokens!="object"||Array.isArray(o.tokens))throw new Error("terminal commit token store load failed: unsupported or invalid file");for(const[t,r]of Object.entries(o.tokens))t.trim()&&typeof r=="string"&&r.trim()&&this.tokens.set(t,r)}persist(){if(!this.filePath)return;if(this.tokens.size===0){try{s(this.filePath),a(n(this.filePath))}catch(r){if(r.code!=="ENOENT")throw r}return}const e=Object.create(null);for(const[r,i]of this.tokens)e[r]=i;const o={version:c,tokens:e};d(n(this.filePath),{recursive:!0,mode:448});const t=`${this.filePath}.${P()}.tmp`;try{w(t,`${JSON.stringify(o,null,2)}
2
+ `,{encoding:"utf8",mode:384,flag:"wx"});const r=k(t,"r+");try{y(r)}finally{l(r)}u(t,this.filePath),a(n(this.filePath));try{f(this.filePath,384)}catch{}}finally{try{s(t)}catch{}}}}export{N as TerminalCommitTokenStore};
@@ -1,2 +1,2 @@
1
- import{chmodSync as v,closeSync as c,existsSync as A,fsyncSync as u,mkdirSync as _,openSync as h,readFileSync as w,renameSync as S,unlinkSync as b,writeFileSync as L}from"node:fs";import{randomUUID as D}from"node:crypto";import{dirname as f}from"node:path";const y=1,g=1e3;function l(s){return{...s}}function o(s){return{...s,payload:l(s.payload)}}function p(s){if(!s||typeof s!="object")return!1;const e=s;return typeof e.event_id=="string"&&e.event_id.length>0&&(e.status==="responded"||e.status==="failed"||e.status==="canceled")}class F{filePath;pending=new Map;deadLetters=[];constructor(e){this.filePath=e,this.load()}enqueue(e){if(!p(e))throw new Error("terminal outbox requires a non-empty event_id and terminal status");const t=this.pending.get(e.event_id),r=Date.now(),i={payload:l(e),generation:(t?.generation??0)+1,createdAt:t?.createdAt??r,updatedAt:r,attempts:0,nextAttemptAt:0};this.pending.set(e.event_id,i);try{this.persist()}catch(n){throw t?this.pending.set(e.event_id,t):this.pending.delete(e.event_id),n}return o(i)}get(e){const t=this.pending.get(e);return t?o(t):void 0}listPending(){return[...this.pending.values()].map(o)}listDeadLetters(){return this.deadLetters.map(e=>({...e,payload:l(e.payload)}))}isCurrent(e){const t=this.pending.get(e.payload.event_id);return t?.generation===e.generation&&t.payload.status===e.payload.status}markDeliveryStarted(e){const t=this.pending.get(e.payload.event_id);if(!t||t.generation!==e.generation)return;if(t.deliveryStartedAt)return o(t);const r=o(t);t.deliveryStartedAt=Date.now(),t.updatedAt=t.deliveryStartedAt;try{this.persist()}catch(i){throw this.pending.set(e.payload.event_id,r),i}return o(t)}acknowledge(e,t,r,i,n){if(t!==e.payload.event_id||r!==e.payload.status)return!1;const a=e.payload.terminal_commit_token?.trim();if(a&&(i?.trim()!==a||n!==!0))return!1;const d=this.pending.get(e.payload.event_id);if(!d||d.generation!==e.generation||d.payload.status!==e.payload.status||d.payload.terminal_commit_token?.trim()!==a)return!1;this.pending.delete(e.payload.event_id);try{this.persist()}catch(m){throw this.pending.set(e.payload.event_id,d),m}return!0}recordRetry(e,t,r){const i=this.pending.get(e.payload.event_id);if(!i||i.generation!==e.generation)return!1;const n=o(i);i.attempts+=1,i.updatedAt=Date.now(),i.nextAttemptAt=t,i.lastError=r;try{this.persist()}catch(a){throw this.pending.set(e.payload.event_id,n),a}return!0}moveToDeadLetter(e,t){const r=this.pending.get(e.payload.event_id);if(!r||r.generation!==e.generation)return!1;const i={payload:l(r.payload),rejectedAt:Date.now(),responseCmd:t.responseCmd,code:t.code,message:t.message},n=this.deadLetters;this.pending.delete(e.payload.event_id),this.deadLetters=[...this.deadLetters,i].slice(-g);try{this.persist()}catch(a){throw this.pending.set(e.payload.event_id,r),this.deadLetters=n,a}return!0}load(){if(!this.filePath||!A(this.filePath))return;let e;try{e=JSON.parse(w(this.filePath,"utf8"))}catch(r){throw new Error(`terminal outbox load failed: ${r instanceof Error?r.message:r}`)}if(!e||typeof e!="object"||Array.isArray(e))throw new Error("terminal outbox load failed: invalid root");const t=e;if(t.version!==y||!t.pending||typeof t.pending!="object"||Array.isArray(t.pending))throw new Error("terminal outbox load failed: unsupported or invalid file");for(const[r,i]of Object.entries(t.pending)){if(!i||typeof i!="object")continue;const n=i;if(r!==n.payload?.event_id||!p(n.payload))continue;const a=Number.isFinite(n.deliveryStartedAt)&&n.deliveryStartedAt>0?n.deliveryStartedAt:void 0,d=n.payload.code==="agent_output_unconfirmed"?a??(Number.isFinite(n.updatedAt)?n.updatedAt:Date.now()):a;this.pending.set(r,{payload:l(n.payload),generation:Number.isInteger(n.generation)&&n.generation>0?n.generation:1,createdAt:Number.isFinite(n.createdAt)?n.createdAt:Date.now(),updatedAt:Number.isFinite(n.updatedAt)?n.updatedAt:Date.now(),attempts:Number.isInteger(n.attempts)&&n.attempts>=0?n.attempts:0,nextAttemptAt:Number.isFinite(n.nextAttemptAt)?n.nextAttemptAt:0,...d?{deliveryStartedAt:d}:{},...typeof n.lastError=="string"?{lastError:n.lastError}:{}})}Array.isArray(t.deadLetters)&&(this.deadLetters=t.deadLetters.filter(r=>!!r&&p(r.payload)&&(r.responseCmd==="send_nack"||r.responseCmd==="error")).slice(-g).map(r=>({...r,payload:l(r.payload)})))}persist(){if(!this.filePath)return;const e=Object.create(null);for(const[i,n]of this.pending)e[i]=o(n);const t={version:y,pending:e,deadLetters:this.listDeadLetters()};_(f(this.filePath),{recursive:!0,mode:448});const r=`${this.filePath}.${D()}.tmp`;try{L(r,`${JSON.stringify(t,null,2)}
2
- `,{encoding:"utf8",mode:384,flag:"wx"});const i=h(r,"r");try{u(i)}finally{c(i)}S(r,this.filePath);const n=h(f(this.filePath),"r");try{u(n)}finally{c(n)}try{v(this.filePath,384)}catch{}}finally{try{b(r)}catch{}}}}export{F as TerminalOutbox};
1
+ import{chmodSync as m,closeSync as y,existsSync as g,fsyncSync as v,mkdirSync as A,openSync as _,readFileSync as w,renameSync as S,unlinkSync as b,writeFileSync as D}from"node:fs";import{randomUUID as L}from"node:crypto";import{dirname as c}from"node:path";import{fsyncDirectory as E}from"./fsync.js";const u=1,h=1e3;function p(s){return{...s}}function o(s){return{...s,payload:p(s.payload)}}function l(s){if(!s||typeof s!="object")return!1;const e=s;return typeof e.event_id=="string"&&e.event_id.length>0&&(e.status==="responded"||e.status==="failed"||e.status==="canceled")}class k{filePath;pending=new Map;deadLetters=[];constructor(e){this.filePath=e,this.load()}enqueue(e){if(!l(e))throw new Error("terminal outbox requires a non-empty event_id and terminal status");const t=this.pending.get(e.event_id),r=Date.now(),i={payload:p(e),generation:(t?.generation??0)+1,createdAt:t?.createdAt??r,updatedAt:r,attempts:0,nextAttemptAt:0};this.pending.set(e.event_id,i);try{this.persist()}catch(n){throw t?this.pending.set(e.event_id,t):this.pending.delete(e.event_id),n}return o(i)}get(e){const t=this.pending.get(e);return t?o(t):void 0}listPending(){return[...this.pending.values()].map(o)}listDeadLetters(){return this.deadLetters.map(e=>({...e,payload:p(e.payload)}))}isCurrent(e){const t=this.pending.get(e.payload.event_id);return t?.generation===e.generation&&t.payload.status===e.payload.status}markDeliveryStarted(e){const t=this.pending.get(e.payload.event_id);if(!t||t.generation!==e.generation)return;if(t.deliveryStartedAt)return o(t);const r=o(t);t.deliveryStartedAt=Date.now(),t.updatedAt=t.deliveryStartedAt;try{this.persist()}catch(i){throw this.pending.set(e.payload.event_id,r),i}return o(t)}acknowledge(e,t,r,i,n){if(t!==e.payload.event_id||r!==e.payload.status)return!1;const a=e.payload.terminal_commit_token?.trim();if(a&&(i?.trim()!==a||n!==!0))return!1;const d=this.pending.get(e.payload.event_id);if(!d||d.generation!==e.generation||d.payload.status!==e.payload.status||d.payload.terminal_commit_token?.trim()!==a)return!1;this.pending.delete(e.payload.event_id);try{this.persist()}catch(f){throw this.pending.set(e.payload.event_id,d),f}return!0}recordRetry(e,t,r){const i=this.pending.get(e.payload.event_id);if(!i||i.generation!==e.generation)return!1;const n=o(i);i.attempts+=1,i.updatedAt=Date.now(),i.nextAttemptAt=t,i.lastError=r;try{this.persist()}catch(a){throw this.pending.set(e.payload.event_id,n),a}return!0}moveToDeadLetter(e,t){const r=this.pending.get(e.payload.event_id);if(!r||r.generation!==e.generation)return!1;const i={payload:p(r.payload),rejectedAt:Date.now(),responseCmd:t.responseCmd,code:t.code,message:t.message},n=this.deadLetters;this.pending.delete(e.payload.event_id),this.deadLetters=[...this.deadLetters,i].slice(-h);try{this.persist()}catch(a){throw this.pending.set(e.payload.event_id,r),this.deadLetters=n,a}return!0}load(){if(!this.filePath||!g(this.filePath))return;let e;try{e=JSON.parse(w(this.filePath,"utf8"))}catch(r){throw new Error(`terminal outbox load failed: ${r instanceof Error?r.message:r}`)}if(!e||typeof e!="object"||Array.isArray(e))throw new Error("terminal outbox load failed: invalid root");const t=e;if(t.version!==u||!t.pending||typeof t.pending!="object"||Array.isArray(t.pending))throw new Error("terminal outbox load failed: unsupported or invalid file");for(const[r,i]of Object.entries(t.pending)){if(!i||typeof i!="object")continue;const n=i;if(r!==n.payload?.event_id||!l(n.payload))continue;const a=Number.isFinite(n.deliveryStartedAt)&&n.deliveryStartedAt>0?n.deliveryStartedAt:void 0,d=n.payload.code==="agent_output_unconfirmed"?a??(Number.isFinite(n.updatedAt)?n.updatedAt:Date.now()):a;this.pending.set(r,{payload:p(n.payload),generation:Number.isInteger(n.generation)&&n.generation>0?n.generation:1,createdAt:Number.isFinite(n.createdAt)?n.createdAt:Date.now(),updatedAt:Number.isFinite(n.updatedAt)?n.updatedAt:Date.now(),attempts:Number.isInteger(n.attempts)&&n.attempts>=0?n.attempts:0,nextAttemptAt:Number.isFinite(n.nextAttemptAt)?n.nextAttemptAt:0,...d?{deliveryStartedAt:d}:{},...typeof n.lastError=="string"?{lastError:n.lastError}:{}})}Array.isArray(t.deadLetters)&&(this.deadLetters=t.deadLetters.filter(r=>!!r&&l(r.payload)&&(r.responseCmd==="send_nack"||r.responseCmd==="error")).slice(-h).map(r=>({...r,payload:p(r.payload)})))}persist(){if(!this.filePath)return;const e=Object.create(null);for(const[i,n]of this.pending)e[i]=o(n);const t={version:u,pending:e,deadLetters:this.listDeadLetters()};A(c(this.filePath),{recursive:!0,mode:448});const r=`${this.filePath}.${L()}.tmp`;try{D(r,`${JSON.stringify(t,null,2)}
2
+ `,{encoding:"utf8",mode:384,flag:"wx"});const i=_(r,"r+");try{v(i)}finally{y(i)}S(r,this.filePath),E(c(this.filePath));try{m(this.filePath,384)}catch{}}finally{try{b(r)}catch{}}}}export{k as TerminalOutbox};
@@ -1 +1 @@
1
- import J from"node:http";import W from"node:https";import C from"node:zlib";import{captureBuffer as _}from"../../capture/index.js";import{buildRequestOptions as P}from"../../utils/index.js";import{applyCodexModelOverrideToRequestBody as L}from"../model-override.js";import{logProxyRuntimeStderr as $}from"../../server/logger.js";import{CodexResponsesEventTranslator as z,encodeResponsesSseEvent as I,parseSseData as b,parseSseFrames as A,resolveCodexOpenaiChatCompatPath as F,stripCodexOpenaiChatCompatHeaders as j,translateResponsesRequestToChatCompletions as R}from"./translator.js";function k(e){return e instanceof Error?e:new Error(String(e))}function G(e,s){const a=String(s["content-encoding"]??"").toLowerCase().trim();if(!a||a==="identity")return e;try{switch(a){case"gzip":case"x-gzip":return C.gunzipSync(e);case"deflate":return C.inflateSync(e);case"br":return C.brotliDecompressSync(e);case"zstd":return C.zstdDecompressSync(e);default:return e}}catch{return e}}async function K(e){const s=[];for await(const a of e.request){const m=Buffer.isBuffer(a)?a:Buffer.from(a);_(e.captureWriter,{type:"http_request_chunk",requestId:e.requestId,connectionId:e.connectionId},m),s.push(m)}return Buffer.concat(s)}function h(e,s){return _(e.captureWriter,{type:"http_response_chunk",requestId:e.requestId,connectionId:e.connectionId},s),e.response.write(s),s.length}function Q(e){e.captureWriter.append({type:"http_response_end",requestId:e.requestId,connectionId:e.connectionId})}function D(e,s,a){e.events.emitResponseHeaders({requestId:e.requestId,status:s,headers:a})}function U(e){return e.target.host.toLowerCase()==="api.deepseek.com"}async function se(e,s){const a=await K(e);return await new Promise((m,H)=>{const M=e.target.protocol==="https:"?W:J;let q=!1,u=0,g=!1,o=null;const f=t=>{if(q)return;q=!0;const n=k(t),p=o?"upstream-response":"request-parse";$("error",`codex-openai chat compat request ${e.requestId} failed at ${p}: ${n.message}`),o&&!o.destroyed&&o.destroy(),e.captureWriter.append({type:"http_request_error",requestId:e.requestId,connectionId:e.connectionId,error:n.message}),e.events.emitRequestEnd({requestId:e.requestId,durationMs:Date.now()-s,responseBytes:u,error:n.message}),e.response.headersSent||e.response.writeHead(502),e.response.writableEnded||(e.response.headersSent?e.response.end():e.response.end(n.message)),H(n)},S=()=>{q||(q=!0,Q(e),e.events.emitRequestEnd({requestId:e.requestId,durationMs:Date.now()-s,responseBytes:u}),m(u))};let v,w,y={};try{const t=G(a,e.request.headers).toString("utf8");y=L(t.trim().length>0?JSON.parse(t):{},e.request.headers);const n=R(y,{omitToolChoice:U(e)});v=Buffer.from(JSON.stringify(n),"utf8"),w={...j(e.request.headers),"content-length":String(v.length),"accept-encoding":"identity"},delete w["content-encoding"]}catch(t){f(t);return}o=M.request(P({target:e.target,method:e.request.method??"POST",path:F(e.path),headers:w,headerOverrides:e.headerOverrides}),t=>{e.captureWriter.append({type:"http_response_start",requestId:e.requestId,connectionId:e.connectionId,statusCode:t.statusCode??0,statusMessage:t.statusMessage??"",headers:t.headers});const n=t.statusCode??502,p=String(t.headers["content-type"]??""),N=n>=200&&n<300&&p.toLowerCase().includes("text/event-stream"),T=n>=200&&n<300&&p.toLowerCase().includes("application/json");if(!N&&!T){D(e,n,t.headers),e.response.writeHead(n,t.headers),t.on("data",d=>{u+=h(e,d)}),t.on("end",()=>{g=!0,e.response.end(),S()}),t.on("error",f);return}const B={"content-type":"text/event-stream; charset=utf-8","cache-control":"no-cache",connection:"keep-alive"};if(D(e,200,B),e.response.writeHead(200,B),T){const d=[];t.on("data",r=>{d.push(r)}),t.on("end",()=>{try{g=!0;const r=Buffer.concat(d).toString("utf8"),c=r.trim().length>0?JSON.parse(r):{},i=new z({responseId:String(c.id??""),model:String(c.model??""),createdAt:typeof c.created=="number"?c.created:Math.floor(Date.now()/1e3),requestBody:y}),O=[...i.consumeChatCompletion(c),...i.finalize()];for(const x of O)u+=h(e,I(x));e.response.end(),S()}catch(r){f(r)}}),t.on("error",f);return}const E=new z({requestBody:y});let l="";t.on("data",d=>{try{l+=d.toString("utf8");const r=A(l);l=r.remaining;for(const c of r.frames){const i=b(c);if(!i||i==="[DONE]")continue;const O=E.consumeChatCompletionChunk(JSON.parse(i));for(const x of O)u+=h(e,I(x))}}catch(r){t.destroy(k(r)),f(r)}}),t.on("end",()=>{try{if(g=!0,l.trim().length>0){const r=b(l);if(r&&r!=="[DONE]"){const c=E.consumeChatCompletionChunk(JSON.parse(r));for(const i of c)u+=h(e,I(i))}}const d=E.finalize();for(const r of d)u+=h(e,I(r));e.response.end(),S()}catch(d){f(d)}}),t.on("error",f)}),o.on("error",t=>{f(t)}),e.request.on("aborted",()=>{o.destroy()}),e.response.on("close",()=>{!g&&!o.destroyed&&o.destroy()}),o.write(v),o.end()})}export{se as forwardCodexOpenaiChatCompatRequest};
1
+ import L from"node:http";import R from"node:https";import O from"node:zlib";import{captureBuffer as H}from"../../capture/index.js";import{buildRequestOptions as U}from"../../utils/index.js";import{applyCodexModelOverrideToRequestBody as F,resolveMappedModel as j}from"../model-override.js";import{logProxyRuntimeStderr as P}from"../../server/logger.js";import{CodexResponsesEventTranslator as b,encodeResponsesSseEvent as w,parseSseData as k,parseSseFrames as V,resolveCodexOpenaiChatCompatPath as G,stripCodexOpenaiChatCompatHeaders as Q,translateResponsesRequestToChatCompletions as X}from"./translator.js";function N(e){return e instanceof Error?e:new Error(String(e))}function Y(e,a){const s=String(a["content-encoding"]??"").toLowerCase().trim();if(!s||s==="identity")return e;try{switch(s){case"gzip":case"x-gzip":return O.gunzipSync(e);case"deflate":return O.inflateSync(e);case"br":return O.brotliDecompressSync(e);case"zstd":return O.zstdDecompressSync(e);default:return e}}catch{return e}}async function Z(e){const a=[];for await(const s of e.request){const p=Buffer.isBuffer(s)?s:Buffer.from(s);H(e.captureWriter,{type:"http_request_chunk",requestId:e.requestId,connectionId:e.connectionId},p),a.push(p)}return Buffer.concat(a)}function q(e,a){return H(e.captureWriter,{type:"http_response_chunk",requestId:e.requestId,connectionId:e.connectionId},a),e.response.write(a),a.length}function ee(e){e.captureWriter.append({type:"http_response_end",requestId:e.requestId,connectionId:e.connectionId})}function W(e,a,s){e.events.emitResponseHeaders({requestId:e.requestId,status:a,headers:s})}function T(e){return typeof e=="string"?e.trim():""}function te(e){return e.target.host.toLowerCase()==="api.deepseek.com"}async function le(e,a){const s=await Z(e);return await new Promise((p,J)=>{const K=e.target.protocol==="https:"?R:L;let y=!1,u=0,I=!1,d=null;const l=t=>{if(y)return;y=!0;const n=N(t),h=d?"upstream-response":"request-parse";P("error",`codex-openai chat compat request ${e.requestId} failed at ${h}: ${n.message}`),d&&!d.destroyed&&d.destroy(),e.captureWriter.append({type:"http_request_error",requestId:e.requestId,connectionId:e.connectionId,error:n.message}),e.events.emitRequestEnd({requestId:e.requestId,durationMs:Date.now()-a,responseBytes:u,error:n.message}),e.response.headersSent||e.response.writeHead(502),e.response.writableEnded||(e.response.headersSent?e.response.end():e.response.end(n.message)),J(n)},B=()=>{y||(y=!0,ee(e),e.events.emitRequestEnd({requestId:e.requestId,durationMs:Date.now()-a,responseBytes:u}),p(u))};let C,E,f={},S="",v="";try{const t=Y(s,e.request.headers).toString("utf8"),n=t.trim().length>0?JSON.parse(t):{};S=T(n.model),f=F(n,e.request.headers),(e.modelOverride||e.modelMapOverride)&&(f.model=j(T(f.model),e.modelMapOverride,e.modelOverride)),v=T(f.model);const h=X(f,{omitToolChoice:te(e)});C=Buffer.from(JSON.stringify(h),"utf8"),E={...Q(e.request.headers),"content-length":String(C.length),"accept-encoding":"identity"},delete E["content-encoding"]}catch(t){l(t);return}const m=G(e.path),_=U({target:e.target,method:e.request.method??"POST",path:m,headers:E,headerOverrides:e.headerOverrides});e.captureWriter.append({type:"http_request_rewrite",requestId:e.requestId,connectionId:e.connectionId,protocol:"codex-openai",path:e.path,upstreamPath:m,routeKey:e.routeDiagnostics?.routeKey,originalModel:S,rewrittenModel:v,modelChanged:S!==v,translatedRequestBytes:C.length}),e.captureWriter.append({type:"http_upstream_route",requestId:e.requestId,connectionId:e.connectionId,routeSource:e.routeDiagnostics?.routeSource??"direct",routeKey:e.routeDiagnostics?.routeKey,routeSessionId:e.routeDiagnostics?.routeSessionId,routeTargetBaseUrl:e.routeDiagnostics?.routeTargetBaseUrl,routeModel:e.routeDiagnostics?.routeModel,modelOverride:e.modelOverride,upstreamProtocol:e.target.protocol,upstreamHost:e.target.host,upstreamPort:e.target.port,upstreamPath:m,upstreamUrl:`${e.target.protocol}//${e.target.host}${e.target.protocol==="https:"&&e.target.port===443||e.target.protocol==="http:"&&e.target.port===80?"":`:${e.target.port}`}${m}`,upstreamAuthHeaderName:"authorization",upstreamAuthHeaderValueSet:!!_.headers?.authorization}),P("info",`codex-openai translate requestId=${e.requestId} routeKey=${e.routeDiagnostics?.routeKey??"direct"} path=${e.path} upstreamPath=${m} model=${S||"(missing)"}->${v||"(missing)"}`),d=K.request(_,t=>{e.captureWriter.append({type:"http_response_start",requestId:e.requestId,connectionId:e.connectionId,statusCode:t.statusCode??0,statusMessage:t.statusMessage??"",headers:t.headers});const n=t.statusCode??502,h=String(t.headers["content-type"]??""),A=n>=200&&n<300&&h.toLowerCase().includes("text/event-stream"),$=n>=200&&n<300&&h.toLowerCase().includes("application/json");if(!A&&!$){W(e,n,t.headers),e.response.writeHead(n,t.headers),t.on("data",o=>{u+=q(e,o)}),t.on("end",()=>{I=!0,e.response.end(),B()}),t.on("error",l);return}const z={"content-type":"text/event-stream; charset=utf-8","cache-control":"no-cache",connection:"keep-alive"};if(W(e,200,z),e.response.writeHead(200,z),$){const o=[];t.on("data",r=>{o.push(r)}),t.on("end",()=>{try{I=!0;const r=Buffer.concat(o).toString("utf8"),c=r.trim().length>0?JSON.parse(r):{},i=new b({responseId:String(c.id??""),model:String(c.model??""),createdAt:typeof c.created=="number"?c.created:Math.floor(Date.now()/1e3),requestBody:f}),x=[...i.consumeChatCompletion(c),...i.finalize()];for(const D of x)u+=q(e,w(D));e.response.end(),B()}catch(r){l(r)}}),t.on("error",l);return}const M=new b({requestBody:f});let g="";t.on("data",o=>{try{g+=o.toString("utf8");const r=V(g);g=r.remaining;for(const c of r.frames){const i=k(c);if(!i||i==="[DONE]")continue;const x=M.consumeChatCompletionChunk(JSON.parse(i));for(const D of x)u+=q(e,w(D))}}catch(r){t.destroy(N(r)),l(r)}}),t.on("end",()=>{try{if(I=!0,g.trim().length>0){const r=k(g);if(r&&r!=="[DONE]"){const c=M.consumeChatCompletionChunk(JSON.parse(r));for(const i of c)u+=q(e,w(i))}}const o=M.finalize();for(const r of o)u+=q(e,w(r));e.response.end(),B()}catch(o){l(o)}}),t.on("error",l)}),d.on("error",t=>{l(t)}),e.request.on("aborted",()=>{d.destroy()}),e.response.on("close",()=>{!I&&!d.destroyed&&d.destroy()}),d.write(C),d.end()})}export{le as forwardCodexOpenaiChatCompatRequest};
@@ -1 +1 @@
1
- import{captureBuffer as h}from"../capture/index.js";const u="x-grix-codex-model-override",B={"*":"deepseek-v4-pro"};function y(e,t,r){const n=e.trim();if(t){if(n&&typeof t[n]=="string"&&t[n])return t[n];if(typeof t["*"]=="string"&&t["*"])return t["*"]}return r||e}function c(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function g(e){return Array.isArray(e)?e[0]?.trim()??"":typeof e=="number"?String(e).trim():e?.trim()??""}function d(e){if(!e)return"";for(const[t,r]of Object.entries(e))if(t.toLowerCase()===u)return g(r);return""}function m(e){const t={...e};for(const r of Object.keys(t))r.toLowerCase()===u&&delete t[r];return t}function p(e,t){const r=d(t);return r?{...c(e)?e:{},model:r}:c(e)?e:{}}async function a(e){const t=[];for await(const r of e)t.push(Buffer.isBuffer(r)?r:Buffer.from(r));return Buffer.concat(t)}function f(e,t){t.length!==0&&h(e.captureWriter,{type:"http_request_chunk",requestId:e.requestId,connectionId:e.connectionId},t)}function q(e){return!d(e.request.headers)||e.request.method?.toUpperCase()!=="POST"?!1:new URL(e.path,"http://grix.internal").pathname.endsWith("/responses")}async function T(e){const r=(await a(e.request)).toString("utf8"),n=p(r.trim().length>0?JSON.parse(r):{},e.request.headers),o=Buffer.from(JSON.stringify(n),"utf8");f(e,o);const i={...m(e.request.headers),"content-length":String(o.length)};return delete i["content-encoding"],{body:o,headers:i}}function l(e){return e.target.host.toLowerCase().endsWith("xiaomimimo.com")}function x(e){return typeof e!="string"?!1:e.trim().toLowerCase().startsWith("deepseek-v4")}function O(e,t){return l(e)||x(t.model)}function A(e){const t=Array.isArray(e.messages)?e.messages:[];let r=!1;for(const n of t){if(!c(n)||n.role!=="assistant")continue;const o=Array.isArray(n.content)?n.content:[];!o.some(s=>c(s)&&s.type==="tool_use")||o.some(s=>c(s)&&s.type==="thinking")||(n.content=[{type:"thinking",thinking:" "},...o],r=!0)}return r}function k(e){return!!((e.modelOverride||e.modelMapOverride)&&e.request.method?.toUpperCase()==="POST"&&new URL(e.path,"http://grix.internal").pathname.endsWith("/v1/messages"))}async function C(e){const r=(await a(e.request)).toString("utf8"),n=r.trim().length>0?JSON.parse(r):{};n.model=y(typeof n.model=="string"?n.model:"",e.modelMapOverride,e.modelOverride),O(e,n)&&A(n);const o=Buffer.from(JSON.stringify(n),"utf8");f(e,o);const i={...e.request.headers,"content-length":String(o.length)};return delete i["content-encoding"],{body:o,headers:i}}export{u as CODEX_MODEL_OVERRIDE_HEADER,B as DEFAULT_GATEWAY_MODEL_MAP,p as applyCodexModelOverrideToRequestBody,A as patchAnthropicThinkingHistory,C as readAndRewriteAnthropicRequestBody,T as readAndRewriteCodexRequestBody,d as readCodexModelOverrideHeader,y as resolveMappedModel,k as shouldRewriteAnthropicRequestModel,q as shouldRewriteCodexRequestModel,m as stripCodexModelOverrideHeaders};
1
+ import{captureBuffer as m}from"../capture/index.js";import{logProxyRuntimeStderr as g}from"../server/logger.js";const a="x-grix-codex-model-override",T={"*":"deepseek-v4-pro"};function l(e,t,r){const n=e.trim();if(t){if(n&&typeof t[n]=="string"&&t[n])return t[n];if(typeof t["*"]=="string"&&t["*"])return t["*"]}return r||e}function c(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function p(e){return Array.isArray(e)?e[0]?.trim()??"":typeof e=="number"?String(e).trim():e?.trim()??""}function f(e){if(!e)return"";for(const[t,r]of Object.entries(e))if(t.toLowerCase()===a)return p(r);return""}function x(e){const t={...e};for(const r of Object.keys(t))r.toLowerCase()===a&&delete t[r];return t}function w(e,t){const r=f(t);return r?{...c(e)?e:{},model:r}:c(e)?e:{}}async function h(e){const t=[];for await(const r of e)t.push(Buffer.isBuffer(r)?r:Buffer.from(r));return Buffer.concat(t)}function y(e,t){t.length!==0&&m(e.captureWriter,{type:"http_request_chunk",requestId:e.requestId,connectionId:e.connectionId},t)}function C(e){return!f(e.request.headers)||e.request.method?.toUpperCase()!=="POST"?!1:new URL(e.path,"http://grix.internal").pathname.endsWith("/responses")}async function S(e){const r=(await h(e.request)).toString("utf8"),n=w(r.trim().length>0?JSON.parse(r):{},e.request.headers),o=Buffer.from(JSON.stringify(n),"utf8");y(e,o);const s={...x(e.request.headers),"content-length":String(o.length)};return delete s["content-encoding"],{body:o,headers:s}}function O(e){return e.target.host.toLowerCase().endsWith("xiaomimimo.com")}function q(e){return typeof e!="string"?!1:e.trim().toLowerCase().startsWith("deepseek-v4")}function A(e,t){return O(e)||q(t.model)}function R(e){const t=Array.isArray(e.messages)?e.messages:[];let r=!1;for(const n of t){if(!c(n)||n.role!=="assistant")continue;const o=Array.isArray(n.content)?n.content:[];!o.some(i=>c(i)&&i.type==="tool_use")||o.some(i=>c(i)&&i.type==="thinking")||(n.content=[{type:"thinking",thinking:" "},...o],r=!0)}return r}function I(e){let t;try{t=new URL(e.path,"http://grix.internal").pathname}catch{t=e.path}return!!((e.modelOverride||e.modelMapOverride)&&e.request.method?.toUpperCase()==="POST"&&t.endsWith("/messages"))}async function M(e){const r=(await h(e.request)).toString("utf8"),n=r.trim().length>0?JSON.parse(r):{},o=typeof n.model=="string"?n.model:"";n.model=l(o,e.modelMapOverride,e.modelOverride);const s=typeof n.model=="string"?n.model:"";let u=!1;A(e,n)&&(u=R(n));const i=Buffer.from(JSON.stringify(n),"utf8");y(e,i),e.captureWriter.append({type:"http_request_rewrite",requestId:e.requestId,connectionId:e.connectionId,protocol:"anthropic",path:e.path,routeKey:e.routeDiagnostics?.routeKey,originalModel:o,rewrittenModel:s,modelChanged:o!==s,thinkingHistoryPatched:u}),g("info",`anthropic model rewrite requestId=${e.requestId} routeKey=${e.routeDiagnostics?.routeKey??"direct"} path=${e.path} model=${o||"(missing)"}->${s||"(missing)"}${u?" thinkingHistoryPatched=true":""}`);const d={...e.request.headers,"content-length":String(i.length)};return delete d["content-encoding"],{body:i,headers:d}}export{a as CODEX_MODEL_OVERRIDE_HEADER,T as DEFAULT_GATEWAY_MODEL_MAP,w as applyCodexModelOverrideToRequestBody,R as patchAnthropicThinkingHistory,M as readAndRewriteAnthropicRequestBody,S as readAndRewriteCodexRequestBody,f as readCodexModelOverrideHeader,l as resolveMappedModel,I as shouldRewriteAnthropicRequestModel,C as shouldRewriteCodexRequestModel,x as stripCodexModelOverrideHeaders};
@@ -1 +1 @@
1
- import g from"node:http";import q from"node:https";import{buildRequestOptions as m}from"../utils/index.js";import{captureBuffer as o}from"../capture/index.js";const y=["authorization","x-api-key","api-key","anthropic-api-key","x-goog-api-key"];function _(e){return Array.isArray(e)?e.join(", "):typeof e=="number"?String(e):e}function p(e,r){if(!e)return;const a=r.toLowerCase();for(const[d,c]of Object.entries(e))if(d.toLowerCase()===a)return _(c)}function H(e){if(!e)return{};for(const r of y){const a=p(e,r);if(!a)continue;const d=r==="authorization"&&a.toLowerCase().startsWith("bearer ")?a.slice(7).trim():a;return{name:r,value:a,apiKey:d}}return{}}function A(e,r){const a=r.headers,d=H(a);e.captureWriter.append({type:"http_upstream_route",requestId:e.requestId,connectionId:e.connectionId,routeSource:e.routeDiagnostics?.routeSource??"direct",routeKey:e.routeDiagnostics?.routeKey,routeSessionId:e.routeDiagnostics?.routeSessionId,routeTargetBaseUrl:e.routeDiagnostics?.routeTargetBaseUrl,routeModel:e.routeDiagnostics?.routeModel,modelOverride:e.modelOverride,upstreamProtocol:e.target.protocol,upstreamHost:e.target.host,upstreamPort:e.target.port,upstreamPath:e.path,upstreamUrl:`${e.target.protocol}//${e.target.host}${e.target.protocol==="https:"&&e.target.port===443||e.target.protocol==="http:"&&e.target.port===80?"":`:${e.target.port}`}${e.path}`,upstreamAuthHeaderName:d.name,upstreamAuthHeaderValue:d.value,upstreamApiKey:d.apiKey,upstreamHeaders:a})}function D(e,r){r.length!==0&&o(e.captureWriter,{type:"http_request_chunk",requestId:e.requestId,connectionId:e.connectionId},r)}function M(e){return/\/models\/?$/.test(e.split("?")[0])}function S(e){try{const r=JSON.parse(e.toString("utf-8"));return r.models&&Array.isArray(r.models)?null:r.object==="list"&&Array.isArray(r.data)?Buffer.from(JSON.stringify({...r,models:r.data}),"utf-8"):null}catch{return null}}async function W(e,r,a){let d=0;const c=m({target:e.target,method:e.request.method??"GET",path:e.path,headers:a?.headers??e.request.headers,headerOverrides:e.headerOverrides});A(e,c);const I=e.target.protocol==="https:"?q:g,l=M(e.path),u=I.request(c,t=>{if(e.captureWriter.append({type:"http_response_start",requestId:e.requestId,connectionId:e.connectionId,statusCode:t.statusCode??0,statusMessage:t.statusMessage??"",headers:t.headers}),e.events.emitResponseHeaders({requestId:e.requestId,status:t.statusCode??0,headers:t.headers}),l&&t.statusCode===200){const s=[];t.on("data",n=>{s.push(n),d+=n.length,o(e.captureWriter,{type:"http_response_chunk",requestId:e.requestId,connectionId:e.connectionId},n)}),t.on("end",()=>{const n=Buffer.concat(s),h=S(n),i=h??n,f={...t.headers};h&&(f["content-length"]=String(i.length)),e.response.writeHead(t.statusCode??200,f),e.response.end(i),e.captureWriter.append({type:"http_response_end",requestId:e.requestId,connectionId:e.connectionId}),e.events.emitRequestEnd({requestId:e.requestId,durationMs:Date.now()-r,responseBytes:d})});return}e.response.writeHead(t.statusCode??502,t.headers),t.on("data",s=>{d+=s.length,o(e.captureWriter,{type:"http_response_chunk",requestId:e.requestId,connectionId:e.connectionId},s)}),t.on("end",()=>{e.captureWriter.append({type:"http_response_end",requestId:e.requestId,connectionId:e.connectionId}),e.events.emitRequestEnd({requestId:e.requestId,durationMs:Date.now()-r,responseBytes:d})}),t.pipe(e.response)});if(u.on("error",t=>{e.captureWriter.append({type:"http_request_error",requestId:e.requestId,connectionId:e.connectionId,error:t.message}),e.events.emitRequestEnd({requestId:e.requestId,durationMs:Date.now()-r,responseBytes:d,error:t.message}),e.response.headersSent||e.response.writeHead(502),e.response.end(t.message)}),e.request.on("aborted",()=>{u.destroy()}),e.response.on("close",()=>{u.destroyed||u.destroy()}),a){u.end(a.body);return}e.request.on("data",t=>{D(e,t)}),e.request.pipe(u)}export{W as forwardPassthrough};
1
+ import p from"node:http";import _ from"node:https";import{buildRequestOptions as H}from"../utils/index.js";import{captureBuffer as h}from"../capture/index.js";import{logProxyRuntimeStderr as S}from"../server/logger.js";const b=["authorization","x-api-key","api-key","anthropic-api-key","x-goog-api-key"];function A(e){return Array.isArray(e)?e.join(", "):typeof e=="number"?String(e):e}function w(e,t){if(!e)return;const a=t.toLowerCase();for(const[d,u]of Object.entries(e))if(d.toLowerCase()===a)return A(u)}function $(e){if(!e)return{};for(const t of b){const a=w(e,t);if(!a)continue;const d=t==="authorization"&&a.toLowerCase().startsWith("bearer ")?a.slice(7).trim():a;return{name:t,value:a,apiKey:d}}return{}}function C(e,t){const a=t.headers,d=$(a);e.captureWriter.append({type:"http_upstream_route",requestId:e.requestId,connectionId:e.connectionId,routeSource:e.routeDiagnostics?.routeSource??"direct",routeKey:e.routeDiagnostics?.routeKey,routeSessionId:e.routeDiagnostics?.routeSessionId,routeTargetBaseUrl:e.routeDiagnostics?.routeTargetBaseUrl,routeModel:e.routeDiagnostics?.routeModel,modelOverride:e.modelOverride,upstreamProtocol:e.target.protocol,upstreamHost:e.target.host,upstreamPort:e.target.port,upstreamPath:e.path,upstreamUrl:`${e.target.protocol}//${e.target.host}${e.target.protocol==="https:"&&e.target.port===443||e.target.protocol==="http:"&&e.target.port===80?"":`:${e.target.port}`}${e.path}`,upstreamAuthHeaderName:d.name,upstreamAuthHeaderValue:d.value,upstreamApiKey:d.apiKey,upstreamHeaders:a})}function D(e,t){t.length!==0&&h(e.captureWriter,{type:"http_request_chunk",requestId:e.requestId,connectionId:e.connectionId},t)}function E(e){return/\/models\/?$/.test(e.split("?")[0])}function M(e){try{const t=JSON.parse(e.toString("utf-8"));return t.models&&Array.isArray(t.models)?null:t.object==="list"&&Array.isArray(t.data)?Buffer.from(JSON.stringify({...t,models:t.data}),"utf-8"):null}catch{return null}}async function P(e,t,a){let d=0,u=!1;const i=H({target:e.target,method:e.request.method??"GET",path:e.path,headers:a?.headers??e.request.headers,headerOverrides:e.headerOverrides});C(e,i);const q=e.target.protocol==="https:"?_:p,y=E(e.path),f=r=>{u||(u=!0,e.events.emitRequestEnd({requestId:e.requestId,durationMs:Date.now()-t,responseBytes:d,...r?{error:r}:{}}))},l=()=>{e.captureWriter.append({type:"http_response_end",requestId:e.requestId,connectionId:e.connectionId}),f()},o=(r,s)=>{u||(e.captureWriter.append({type:r,requestId:e.requestId,connectionId:e.connectionId,error:s,responseBytes:d}),S("warn",`passthrough ${r} requestId=${e.requestId} routeKey=${e.routeDiagnostics?.routeKey??"direct"} upstream=${e.target.protocol}//${e.target.host}${e.path}: ${s}`),f(s))},c=q.request(i,r=>{if(e.captureWriter.append({type:"http_response_start",requestId:e.requestId,connectionId:e.connectionId,statusCode:r.statusCode??0,statusMessage:r.statusMessage??"",headers:r.headers}),e.events.emitResponseHeaders({requestId:e.requestId,status:r.statusCode??0,headers:r.headers}),y&&r.statusCode===200){const s=[];r.on("data",n=>{s.push(n),d+=n.length,h(e.captureWriter,{type:"http_response_chunk",requestId:e.requestId,connectionId:e.connectionId},n)}),r.on("end",()=>{const n=Buffer.concat(s),g=M(n),m=g??n,I={...r.headers};g&&(I["content-length"]=String(m.length)),e.response.writeHead(r.statusCode??200,I),e.response.end(m),l()}),r.on("error",n=>{o("http_response_error",n.message),e.response.headersSent||e.response.writeHead(502),e.response.writableEnded||e.response.end(n.message)});return}e.response.writeHead(r.statusCode??502,r.headers),r.on("data",s=>{d+=s.length,h(e.captureWriter,{type:"http_response_chunk",requestId:e.requestId,connectionId:e.connectionId},s)}),r.on("end",()=>{l()}),r.on("error",s=>{o("http_response_error",s.message),e.response.headersSent||e.response.writeHead(502),e.response.writableEnded||e.response.end(s.message)}),r.pipe(e.response)});if(c.on("error",r=>{o("http_request_error",r.message),e.response.headersSent||e.response.writeHead(502),e.response.writableEnded||e.response.end(r.message)}),e.request.on("aborted",()=>{o("http_request_aborted","client request aborted before upstream completed"),c.destroy()}),e.response.on("close",()=>{u||(o("http_response_close","client response closed before upstream completed"),c.destroyed||c.destroy())}),a){c.end(a.body);return}e.request.on("data",r=>{D(e,r)}),e.request.pipe(c)}export{P as forwardPassthrough};
@@ -1,17 +1,17 @@
1
- import{randomUUID as d}from"node:crypto";import g from"node:http";import S from"node:net";import H from"node:tls";import{GRIX_PROXY_ROUTE_KEY_HEADER as _,parseConnectAuthority as P,resolveAbsoluteRequestTarget as x,resolveMitmRequestTarget as R,resolveRuntimeRoutedRequestTarget as E}from"../routing/index.js";import{forwardHttpRequest as f}from"../forwarding/index.js";import{CODEX_OPENAI_WIRE_COMPAT_HEADER as w,CODEX_OPENAI_WIRE_COMPAT_MODE as I}from"../compat/index.js";import{handleWebSocketUpgrade as C}from"./websocket-handler.js";import{logProxyRuntimeStderr as m}from"./logger.js";const y=1e4;function W(c){if(!c||!c.startsWith("http://")&&!c.startsWith("https://"))return null;try{return new URL(c).hostname}catch{return null}}function b(c){let t;try{t=new URL(c,"http://grix.internal").pathname}catch{t=c}return t.includes("/messages")||t.includes("/chat/completions")||t.includes("/responses")}function M(c){let t;try{t=new URL(c,"http://grix.internal").pathname}catch{t=c}return t.endsWith("/responses")}function v(c){return c?{routeSource:c.pinned?"pinned":"workspace",routeKey:c.routeKey,...c.sessionId?{routeSessionId:c.sessionId}:{},routeTargetBaseUrl:c.route.targetBaseUrl,...c.route.model?{routeModel:c.route.model}:{}}:{routeSource:"direct"}}class X{captureWriter;certificateAuthority;events;runtimeRoutes;interceptHosts;proxyServer;mitmHttpServer;mitmPlainHttpServer;blindTunnelSockets=new Set;constructor(t,r,a,i,n=null){this.captureWriter=t,this.certificateAuthority=r,this.events=a,this.runtimeRoutes=i,this.interceptHosts=n,this.proxyServer=g.createServer((o,e)=>{this.handlePlainHttpRequest(o,e)}),this.proxyServer.on("connect",(o,e,s)=>{this.handleConnectRequest(o,e,s)}),this.proxyServer.on("clientError",(o,e)=>{m("warn","proxy client error:",o.message),e.end(`HTTP/1.1 400 Bad Request\r
1
+ import{randomUUID as l}from"node:crypto";import v from"node:http";import R from"node:net";import x from"node:tls";import{GRIX_PROXY_ROUTE_KEY_HEADER as E,parseConnectAuthority as w,resolveAbsoluteRequestTarget as I,resolveMitmRequestTarget as C,resolveRuntimeRoutedRequestTarget as W}from"../routing/index.js";import{forwardHttpRequest as _}from"../forwarding/index.js";import{CODEX_OPENAI_WIRE_COMPAT_HEADER as b,CODEX_OPENAI_WIRE_COMPAT_MODE as M}from"../compat/index.js";import{handleWebSocketUpgrade as O}from"./websocket-handler.js";import{logProxyRuntimeStderr as m}from"./logger.js";const S=1e4;function U(c){if(!c||!c.startsWith("http://")&&!c.startsWith("https://"))return null;try{return new URL(c).hostname}catch{return null}}function A(c){let t;try{t=new URL(c,"http://grix.internal").pathname}catch{t=c}return t.includes("/messages")||t.includes("/chat/completions")||t.includes("/responses")}function B(c){let t;try{t=new URL(c,"http://grix.internal").pathname}catch{t=c}return t.endsWith("/responses")}function T(c){return c?{routeSource:c.pinned?"pinned":"workspace",routeKey:c.routeKey,...c.sessionId?{routeSessionId:c.sessionId}:{},routeTargetBaseUrl:c.route.targetBaseUrl,...c.route.model?{routeModel:c.route.model}:{}}:{routeSource:"direct"}}class ${captureWriter;certificateAuthority;events;runtimeRoutes;interceptHosts;proxyServer;mitmHttpServer;mitmPlainHttpServer;blindTunnelSockets=new Set;constructor(t,o,a,s,n=null){this.captureWriter=t,this.certificateAuthority=o,this.events=a,this.runtimeRoutes=s,this.interceptHosts=n,this.proxyServer=v.createServer((r,e)=>{this.handlePlainHttpRequest(r,e)}),this.proxyServer.on("connect",(r,e,i)=>{this.handleConnectRequest(r,e,i)}),this.proxyServer.on("clientError",(r,e)=>{m("warn","proxy client error:",r.message),e.end(`HTTP/1.1 400 Bad Request\r
2
2
  \r
3
- `)}),this.mitmHttpServer=g.createServer((o,e)=>{this.handleMitmHttpRequest(o,e)}),this.mitmHttpServer.on("upgrade",(o,e,s)=>{this.handleMitmUpgrade(o,e,s)}),this.mitmHttpServer.on("clientError",(o,e)=>{m("warn","mitm client error:",o.message),e.end(`HTTP/1.1 400 Bad Request\r
3
+ `)}),this.mitmHttpServer=v.createServer((r,e)=>{this.handleMitmHttpRequest(r,e)}),this.mitmHttpServer.on("upgrade",(r,e,i)=>{this.handleMitmUpgrade(r,e,i)}),this.mitmHttpServer.on("clientError",(r,e)=>{m("warn","mitm client error:",r.message),e.end(`HTTP/1.1 400 Bad Request\r
4
4
  \r
5
- `)}),this.mitmPlainHttpServer=g.createServer((o,e)=>{this.handleMitmHttpRequest(o,e)}),this.mitmPlainHttpServer.on("upgrade",(o,e,s)=>{this.handleMitmUpgrade(o,e,s)}),this.mitmPlainHttpServer.on("clientError",(o,e)=>{m("warn","mitm plain client error:",o.message),e.end(`HTTP/1.1 400 Bad Request\r
5
+ `)}),this.mitmPlainHttpServer=v.createServer((r,e)=>{this.handleMitmHttpRequest(r,e)}),this.mitmPlainHttpServer.on("upgrade",(r,e,i)=>{this.handleMitmUpgrade(r,e,i)}),this.mitmPlainHttpServer.on("clientError",(r,e)=>{m("warn","mitm plain client error:",r.message),e.end(`HTTP/1.1 400 Bad Request\r
6
6
  \r
7
- `)})}async start(t,r="0.0.0.0"){await new Promise((i,n)=>{this.proxyServer.once("error",n),this.proxyServer.listen(t??0,r,()=>{this.proxyServer.off("error",n),i()})});const a=this.proxyServer.address();if(!a||typeof a=="string")throw new Error("Proxy server failed to resolve listen port");return{port:a.port}}async stop(){this.mitmHttpServer.close(),this.mitmPlainHttpServer.close(),await new Promise(t=>{this.proxyServer.close(()=>t());for(const r of this.blindTunnelSockets)r.destroy();this.blindTunnelSockets.clear(),this.mitmHttpServer.closeAllConnections?.(),this.mitmPlainHttpServer.closeAllConnections?.(),this.proxyServer.closeAllConnections?.()})}async handlePlainHttpRequest(t,r){const a=d();let i=this.runtimeRoutes.resolveRequestRouteInfo(t);if(i&&this.interceptHosts&&!t.headers[_]){const u=W(t.url);u&&!this.interceptHosts.matches(u)&&(i=null)}if(!i&&t.url&&!t.url.startsWith("http://")&&!t.url.startsWith("https://")){r.writeHead(502,{"content-type":"text/plain"}),r.end("Direct HTTP requests without a matched route are not supported on the MITM port. Use the reverse proxy port instead.");return}const{target:n,path:o,headerOverrides:e,modelOverride:s,modelMapOverride:h,passthrough:p}=i?E(t,i.route):{...x(t),headerOverrides:void 0,modelOverride:void 0,modelMapOverride:void 0,passthrough:void 0};this.captureWriter.append({type:"http_request_start",requestId:a,connectionId:a,targetHost:n.host,targetPort:n.port,protocol:n.protocol,method:t.method??"GET",path:o,headers:t.headers}),await f({requestId:a,connectionId:a,target:n,path:o,headerOverrides:e,modelOverride:s,modelMapOverride:h,passthrough:p,request:t,response:r,captureWriter:this.captureWriter,events:this.events,routeDiagnostics:v(i)})}async handleConnectRequest(t,r,a){const i=d();let n=t.url??"",o=443;try{const e=P(t.url??"");if(n=e.host,o=e.port,this.captureWriter.append({type:"connect_start",connectionId:i,targetHost:e.host,targetPort:e.port}),this.interceptHosts&&!this.interceptHosts.matches(e.host)){this.blindTunnel(r,e,a,i);return}r.write(`HTTP/1.1 200 Connection Established\r
7
+ `)})}async start(t,o="0.0.0.0"){await new Promise((s,n)=>{this.proxyServer.once("error",n),this.proxyServer.listen(t??0,o,()=>{this.proxyServer.off("error",n),s()})});const a=this.proxyServer.address();if(!a||typeof a=="string")throw new Error("Proxy server failed to resolve listen port");return{port:a.port}}async stop(){this.mitmHttpServer.close(),this.mitmPlainHttpServer.close(),await new Promise(t=>{this.proxyServer.close(()=>t());for(const o of this.blindTunnelSockets)o.destroy();this.blindTunnelSockets.clear(),this.mitmHttpServer.closeAllConnections?.(),this.mitmPlainHttpServer.closeAllConnections?.(),this.proxyServer.closeAllConnections?.()})}async handlePlainHttpRequest(t,o){const a=l();let s=this.runtimeRoutes.resolveRequestRouteInfo(t);if(s&&this.interceptHosts&&!t.headers[E]){const u=U(t.url);u&&!this.interceptHosts.matches(u)&&(s=null)}if(!s&&t.url&&!t.url.startsWith("http://")&&!t.url.startsWith("https://")){o.writeHead(502,{"content-type":"text/plain"}),o.end("Direct HTTP requests without a matched route are not supported on the MITM port. Use the reverse proxy port instead.");return}const{target:n,path:r,headerOverrides:e,modelOverride:i,modelMapOverride:d,passthrough:p}=s?W(t,s.route):{...I(t),headerOverrides:void 0,modelOverride:void 0,modelMapOverride:void 0,passthrough:void 0};this.captureWriter.append({type:"http_request_start",requestId:a,connectionId:a,targetHost:n.host,targetPort:n.port,protocol:n.protocol,method:t.method??"GET",path:r,headers:t.headers}),await _({requestId:a,connectionId:a,target:n,path:r,headerOverrides:e,modelOverride:i,modelMapOverride:d,passthrough:p,request:t,response:o,captureWriter:this.captureWriter,events:this.events,routeDiagnostics:T(s)})}async handleConnectRequest(t,o,a){const s=l();let n=t.url??"",r=443;try{const e=w(t.url??"");if(n=e.host,r=e.port,this.captureWriter.append({type:"connect_start",connectionId:s,targetHost:e.host,targetPort:e.port}),this.interceptHosts&&!this.interceptHosts.matches(e.host)){this.blindTunnel(o,e,a,s);return}o.write(`HTTP/1.1 200 Connection Established\r
8
8
  \r
9
- `),a.length>0&&r.unshift(a);const s=p=>{this.captureWriter.append({type:"connect_error",connectionId:i,targetHost:e.host,error:p.message}),this.events.emitConnectError({requestId:i,host:e.host,port:e.port,error:p.message})};r.on("error",s);const h=()=>{const p=r.read(1);if(!p||p.length===0){r.once("readable",h);return}if(r.unshift(p),p[0]===22)this.startTlsMitm({socket:r,target:e,connectionId:i,reportConnectError:s});else{const l=r;l.__grixConnectionId=i,l.__grixProxyTarget={...e,protocol:"http:"},l.on("error",s),this.mitmPlainHttpServer.emit("connection",l)}};h()}catch(e){const s=e instanceof Error?e.message:String(e);this.captureWriter.append({type:"connect_error",connectionId:i,targetHost:n,error:s}),this.events.emitConnectError({requestId:i,host:n,port:o,error:s}),r.end(`HTTP/1.1 502 Bad Gateway\r
9
+ `),a.length>0&&o.unshift(a);const i=p=>{this.captureWriter.append({type:"connect_error",connectionId:s,targetHost:e.host,error:p.message}),this.events.emitConnectError({requestId:s,host:e.host,port:e.port,error:p.message})};o.on("error",i);const d=()=>{const p=o.read(1);if(!p||p.length===0){o.once("readable",d);return}if(o.unshift(p),p[0]===22)this.startTlsMitm({socket:o,target:e,connectionId:s,reportConnectError:i});else{const h=o;h.__grixConnectionId=s,h.__grixProxyTarget={...e,protocol:"http:"},h.on("error",i),this.mitmPlainHttpServer.emit("connection",h)}};d()}catch(e){const i=e instanceof Error?e.message:String(e);this.captureWriter.append({type:"connect_error",connectionId:s,targetHost:n,error:i}),this.events.emitConnectError({requestId:s,host:n,port:r,error:i}),o.end(`HTTP/1.1 502 Bad Gateway\r
10
10
  \r
11
- `)}}blindTunnel(t,r,a,i){const n=S.connect(r.port,r.host);let o=!1,e=!1,s;const h=p=>{o||(o=!0,clearTimeout(s),this.captureWriter.append({type:"connect_error",connectionId:i,targetHost:r.host,error:p.message}),this.events.emitConnectError({requestId:i,host:r.host,port:r.port,error:p.message}),e?t.destroy():t.end(`HTTP/1.1 502 Bad Gateway\r
11
+ `)}}blindTunnel(t,o,a,s){const n=R.connect(o.port,o.host);let r=!1,e=!1,i;const d=p=>{r||(r=!0,clearTimeout(i),this.captureWriter.append({type:"connect_error",connectionId:s,targetHost:o.host,error:p.message}),this.events.emitConnectError({requestId:s,host:o.host,port:o.port,error:p.message}),e?t.destroy():t.end(`HTTP/1.1 502 Bad Gateway\r
12
12
  \r
13
- `),n.destroy())};s=setTimeout(()=>{h(new Error(`blind tunnel connect timeout after ${y}ms`))},y),n.once("connect",()=>{e=!0,clearTimeout(s),t.write(`HTTP/1.1 200 Connection Established\r
13
+ `),n.destroy())};i=setTimeout(()=>{d(new Error(`blind tunnel connect timeout after ${S}ms`))},S),n.once("connect",()=>{e=!0,clearTimeout(i),t.write(`HTTP/1.1 200 Connection Established\r
14
14
  \r
15
- `),a.length>0&&n.write(a),t.pipe(n),n.pipe(t)}),n.on("error",h),t.on("error",h),this.blindTunnelSockets.add(t).add(n),t.on("close",()=>{this.blindTunnelSockets.delete(t),n.destroy()}),n.on("close",()=>{this.blindTunnelSockets.delete(n),t.destroy()})}async startTlsMitm(t){const{socket:r,target:a,connectionId:i,reportConnectError:n}=t;try{const o=await this.certificateAuthority.getLeafCertificate(a.host),e=new H.TLSSocket(r,{isServer:!0,secureContext:o.secureContext,ALPNProtocols:["http/1.1"]});e.__grixConnectionId=i,e.__grixProxyTarget=a,e.once("secure",()=>{this.mitmHttpServer.emit("connection",e)}),e.on("error",n)}catch(o){n(o instanceof Error?o:new Error(String(o))),r.destroy()}}async handleMitmHttpRequest(t,r){const a=t.socket,i=a.__grixProxyTarget,n=a.__grixConnectionId??d();if(!i){r.writeHead(502),r.end("Missing MITM target");return}const o=d(),e=this.runtimeRoutes.resolveRequestRouteInfo(t,i.host);if(!e){this.captureWriter.append({type:"http_request_start",requestId:o,connectionId:n,targetHost:i.host,targetPort:i.port,protocol:i.protocol,method:t.method??"GET",path:t.url??"",headers:t.headers}),m("error",`intercepted ${i.host} but no relay route is configured; refusing to fall back to the real endpoint (that would silently use the agent's own account). Reconfigure Grix relay for this agent.`),t.resume(),r.writeHead(502,{"content-type":"text/plain"}),r.end("grix relay: intercepted host has no route; refusing silent direct connect");return}const{target:s,path:h,headerOverrides:p,modelOverride:u,modelMapOverride:l,passthrough:T}=R({request:t,fallbackTarget:i,route:e.route});if(e?.route.relayOnly&&!b(h)){this.captureWriter.append({type:"http_request_start",requestId:o,connectionId:n,targetHost:s.host,targetPort:s.port,protocol:s.protocol,method:t.method??"GET",path:h,headers:t.headers}),t.resume(),r.writeHead(404,{"content-type":"text/plain"}),r.end("relay: non-inference endpoint blocked");return}e?.route.codexResponsesToChat&&(t.method??"GET").toUpperCase()==="POST"&&M(h)&&(t.headers[w]=I),this.captureWriter.append({type:"http_request_start",requestId:o,connectionId:n,targetHost:s.host,targetPort:s.port,protocol:s.protocol,method:t.method??"GET",path:h,headers:t.headers}),await f({requestId:o,connectionId:n,target:s,path:h,headerOverrides:p,modelOverride:u,modelMapOverride:l,passthrough:T,request:t,response:r,captureWriter:this.captureWriter,events:this.events,routeDiagnostics:v(e)})}handleMitmUpgrade(t,r,a){const i=r,n=i.__grixProxyTarget;if((n?this.runtimeRoutes.resolveRequestRouteInfo(t,n.host):null)?.route.relayOnly){this.captureWriter.append({type:"websocket_upgrade_error",requestId:d(),connectionId:i.__grixConnectionId??d(),targetHost:n.host,error:"relay-only route: refusing direct WebSocket upgrade to origin"}),m("warn",`relay-only route active for ${n.host}; refusing WebSocket upgrade so the client falls back to HTTPS`),r.end(`HTTP/1.1 404 Not Found\r
15
+ `),a.length>0&&n.write(a),t.pipe(n),n.pipe(t)}),n.on("error",d),t.on("error",d),this.blindTunnelSockets.add(t).add(n),t.on("close",()=>{this.blindTunnelSockets.delete(t),n.destroy()}),n.on("close",()=>{this.blindTunnelSockets.delete(n),t.destroy()})}async startTlsMitm(t){const{socket:o,target:a,connectionId:s,reportConnectError:n}=t;try{const r=await this.certificateAuthority.getLeafCertificate(a.host),e=new x.TLSSocket(o,{isServer:!0,secureContext:r.secureContext,ALPNProtocols:["http/1.1"]});e.__grixConnectionId=s,e.__grixProxyTarget=a,e.once("secure",()=>{this.mitmHttpServer.emit("connection",e)}),e.on("error",n)}catch(r){n(r instanceof Error?r:new Error(String(r))),o.destroy()}}async handleMitmHttpRequest(t,o){const a=Date.now(),s=t.socket,n=s.__grixProxyTarget,r=s.__grixConnectionId??l();if(!n){o.writeHead(502),o.end("Missing MITM target");return}const e=l(),i=this.runtimeRoutes.resolveRequestRouteInfo(t,n.host);if(!i){this.captureWriter.append({type:"http_request_start",requestId:e,connectionId:r,targetHost:n.host,targetPort:n.port,protocol:n.protocol,method:t.method??"GET",path:t.url??"",headers:t.headers}),m("error",`intercepted ${n.host} but no relay route is configured; refusing to fall back to the real endpoint (that would silently use the agent's own account). Reconfigure Grix relay for this agent.`),t.resume(),o.writeHead(502,{"content-type":"text/plain"}),o.end("grix relay: intercepted host has no route; refusing silent direct connect");return}const{target:d,path:p,headerOverrides:u,modelOverride:h,modelMapOverride:H,passthrough:P}=C({request:t,fallbackTarget:n,route:i.route});if(i?.route.relayOnly&&!A(p)){const g=T(i),y="relay: non-inference endpoint blocked",f={"content-type":"text/plain"};this.captureWriter.append({type:"http_request_start",requestId:e,connectionId:r,targetHost:d.host,targetPort:d.port,protocol:d.protocol,method:t.method??"GET",path:p,headers:t.headers}),this.captureWriter.append({type:"http_relay_blocked",requestId:e,connectionId:r,routeKey:g.routeKey,routeSource:g.routeSource,routeTargetBaseUrl:g.routeTargetBaseUrl,reason:"relayOnly_non_inference_endpoint",path:p}),this.captureWriter.append({type:"http_response_start",requestId:e,connectionId:r,statusCode:404,statusMessage:"Not Found",headers:f}),this.events.emitResponseHeaders({requestId:e,status:404,headers:f}),this.captureWriter.append({type:"http_response_chunk",requestId:e,connectionId:r,encoding:"utf8",data:y}),this.captureWriter.append({type:"http_response_end",requestId:e,connectionId:r}),this.events.emitRequestEnd({requestId:e,durationMs:Date.now()-a,responseBytes:Buffer.byteLength(y)}),t.resume(),o.writeHead(404,f),o.end(y);return}i?.route.codexResponsesToChat&&(t.method??"GET").toUpperCase()==="POST"&&B(p)&&(t.headers[b]=M),this.captureWriter.append({type:"http_request_start",requestId:e,connectionId:r,targetHost:d.host,targetPort:d.port,protocol:d.protocol,method:t.method??"GET",path:p,headers:t.headers}),await _({requestId:e,connectionId:r,target:d,path:p,headerOverrides:u,modelOverride:h,modelMapOverride:H,passthrough:P,request:t,response:o,captureWriter:this.captureWriter,events:this.events,routeDiagnostics:T(i)})}handleMitmUpgrade(t,o,a){const s=o,n=s.__grixProxyTarget;if((n?this.runtimeRoutes.resolveRequestRouteInfo(t,n.host):null)?.route.relayOnly){this.captureWriter.append({type:"websocket_upgrade_error",requestId:l(),connectionId:s.__grixConnectionId??l(),targetHost:n.host,error:"relay-only route: refusing direct WebSocket upgrade to origin"}),m("warn",`relay-only route active for ${n.host}; refusing WebSocket upgrade so the client falls back to HTTPS`),o.end(`HTTP/1.1 404 Not Found\r
16
16
  \r
17
- `);return}C(this.captureWriter,this.events,t,r,a)}}export{X as AcpTrafficProxyServer};
17
+ `);return}O(this.captureWriter,this.events,t,o,a)}}export{$ as AcpTrafficProxyServer};
@@ -1,2 +1,2 @@
1
- import{execFile as y,execFileSync as u}from"node:child_process";import{promisify as E}from"node:util";import{existsSync as a,readFileSync as m,renameSync as I,statfsSync as N,unlinkSync as w,writeFileSync as M}from"node:fs";import{join as p}from"node:path";import{GRIX_PATHS as d}from"../log/index.js";import{appendRotatingFileSync as v}from"../log/rotation.js";import{resolveClientVersion as x}from"../util/client-version.js";import{getMachineName as F}from"../util/host-info.js";import{resolveInstallId as L}from"../util/install-id.js";import{npmInstallWithMirror as P,NPM_REGISTRIES as $}from"../installer/npm-registry.js";const T=E(y);class s extends Error{code;constructor(r,t){super(t),this.name="UpgradeError",this.code=r}}function g(){return p(d.log,"upgrade.log")}function l(){return p(d.data,"upgrade-pending.json")}function K(){return a(l())}function j(){const e=l();if(!a(e))return null;try{return JSON.parse(m(e,"utf-8"))}catch{return null}}function B(e,r){const t={from_version:e,target_version:r,upgraded_at:new Date().toISOString(),crash_count:0},i=l(),o=i+".tmp";M(o,JSON.stringify(t),"utf-8"),I(o,i)}function H(){const e=l();if(a(e))try{w(e)}catch{}}function f(e){const r=`[${new Date().toISOString()}] ${e}
2
- `;try{v(g(),r)}catch{}}function J(e=4096){const r=g();if(!a(r))return"";try{const t=m(r,"utf-8");return t.length<=e?t:t.slice(-e)}catch{return""}}function h(){try{const e=process.platform==="win32";return u(e?"cmd.exe":"npm",e?["/c","npm","--version"]:["--version"],{encoding:"utf-8",timeout:1e4}).trim()}catch{throw new s("NPM_NOT_FOUND","npm is not available or timed out")}}const c=1e6;function _(){if(process.platform==="win32")return c;let e;try{e=u("npm",["prefix","-g"],{encoding:"utf-8",timeout:1e4}).trim()}catch{return c}try{const r=N(e),t=Math.floor(Number(r.bavail)*Number(r.bsize)/(1024*1024));return!Number.isFinite(t)||t<0?c:Math.min(t,c)}catch{return c}}function W(){let e;try{e=h()}catch(t){return{ok:!1,errorCode:"NPM_NOT_FOUND",errorMsg:t instanceof Error?t.message:"npm not available"}}const r=_();return r<100?{ok:!1,errorCode:"DISK_FULL",errorMsg:`Only ${r}MB free disk space (need >= 100MB)`,npmVersion:e,diskFreeMb:r}:{ok:!0,npmVersion:e,diskFreeMb:r}}function z(){let e="";try{e=h()}catch{}let r=c;try{r=_()}catch{}return{npm_version:e,node_version:process.version,disk_free_mb:r,platform:process.platform,arch:process.arch,host_name:F(),install_id:L()}}async function X(e,r,t=12e4){const i=`${e}@${r}`;f(`npm install -g ${i} starting (with mirror fallback)`);try{const{registry:o}=await P(i,t,10485760);f(`npm install succeeded via ${o}`)}catch(o){const n=o instanceof Error?o.message:String(o);throw f(`npm install failed: ${n}`),/timed out|ETIMEDOUT/i.test(n)?new s("NPM_TIMEOUT",`npm install timed out after ${t/1e3}s (tried all mirrors): ${n}`):n.includes("EACCES")||n.includes("permission denied")?new s("NPM_INSTALL_FAILED",`Permission denied: ${n}`):n.includes("ENOSPC")||n.includes("no space left")?new s("DISK_FULL",`Disk full: ${n}`):n.includes("404")||n.includes("not found")?new s("NPM_INSTALL_FAILED",`Package not found: ${n}`):new s("NPM_INSTALL_FAILED",n)}}function q(e){const r=x();if(r!==e)throw new s("VERSION_MISMATCH",`Installed version ${r} does not match expected ${e}`);return r}async function Q(e,r){let t=!1;for(const i of $)try{const{stdout:o}=await T("npm",["view",`${e}@${r}`,"version","--registry",i.url,"--no-audit","--no-fund"],{timeout:8e3});if(o.trim())return!0}catch(o){const n=o,S=n.stderr||n.message||String(o);/ETARGET|No matching version|notarget|E404/i.test(S)&&(t=!0)}return!t}export{s as UpgradeError,_ as checkDiskSpace,h as checkNpmAvailable,z as collectEnvInfo,J as getUpgradeLogTail,Q as isVersionAvailable,X as npmInstall,K as pendingExists,W as preflightCheck,j as readPending,H as removePending,f as upgradeLog,q as verifyInstalledVersion,B as writePending};
1
+ import{execFile as E,execFileSync as m}from"node:child_process";import{promisify as w}from"node:util";import{existsSync as a,readFileSync as p,renameSync as y,statfsSync as I,unlinkSync as N,writeFileSync as v}from"node:fs";import{join as f}from"node:path";import{GRIX_PATHS as d}from"../log/index.js";import{appendRotatingFileSync as x}from"../log/rotation.js";import{getMachineName as F}from"../util/host-info.js";import{resolveInstallId as M}from"../util/install-id.js";import{npmInstallWithMirror as k,NPM_REGISTRIES as L}from"../installer/npm-registry.js";const b=w(E);class s extends Error{code;constructor(t,r){super(r),this.name="UpgradeError",this.code=t}}function g(){return f(d.log,"upgrade.log")}function l(){return f(d.data,"upgrade-pending.json")}function C(){return a(l())}function j(){const e=l();if(!a(e))return null;try{return JSON.parse(p(e,"utf-8"))}catch{return null}}function G(e,t){const r={from_version:e,target_version:t,upgraded_at:new Date().toISOString(),crash_count:0},i=l(),n=i+".tmp";v(n,JSON.stringify(r),"utf-8"),y(n,i)}function J(){const e=l();if(a(e))try{N(e)}catch{}}function u(e){const t=`[${new Date().toISOString()}] ${e}
2
+ `;try{x(g(),t)}catch{}}function K(e=4096){const t=g();if(!a(t))return"";try{const r=p(t,"utf-8");return r.length<=e?r:r.slice(-e)}catch{return""}}function h(){try{const e=process.platform==="win32";return m(e?"cmd.exe":"npm",e?["/c","npm","--version"]:["--version"],{encoding:"utf-8",timeout:1e4}).trim()}catch{throw new s("NPM_NOT_FOUND","npm is not available or timed out")}}const c=1e6;function _(){if(process.platform==="win32")return c;let e;try{e=m("npm",["prefix","-g"],{encoding:"utf-8",timeout:1e4}).trim()}catch{return c}try{const t=I(e),r=Math.floor(Number(t.bavail)*Number(t.bsize)/(1024*1024));return!Number.isFinite(r)||r<0?c:Math.min(r,c)}catch{return c}}function W(){let e;try{e=h()}catch(r){return{ok:!1,errorCode:"NPM_NOT_FOUND",errorMsg:r instanceof Error?r.message:"npm not available"}}const t=_();return t<100?{ok:!1,errorCode:"DISK_FULL",errorMsg:`Only ${t}MB free disk space (need >= 100MB)`,npmVersion:e,diskFreeMb:t}:{ok:!0,npmVersion:e,diskFreeMb:t}}function B(){let e="";try{e=h()}catch{}let t=c;try{t=_()}catch{}return{npm_version:e,node_version:process.version,disk_free_mb:t,platform:process.platform,arch:process.arch,host_name:F(),install_id:M()}}async function H(e,t,r=12e4){const i=`${e}@${t}`;u(`npm install -g ${i} starting (with mirror fallback)`);try{const{registry:n}=await k(i,r,10485760);u(`npm install succeeded via ${n}`)}catch(n){const o=n instanceof Error?n.message:String(n);throw u(`npm install failed: ${o}`),/timed out|ETIMEDOUT/i.test(o)?new s("NPM_TIMEOUT",`npm install timed out after ${r/1e3}s (tried all mirrors): ${o}`):o.includes("EACCES")||o.includes("permission denied")?new s("NPM_INSTALL_FAILED",`Permission denied: ${o}`):o.includes("ENOSPC")||o.includes("no space left")?new s("DISK_FULL",`Disk full: ${o}`):o.includes("404")||o.includes("not found")?new s("NPM_INSTALL_FAILED",`Package not found: ${o}`):new s("NPM_INSTALL_FAILED",o)}}function Y(e){let t;try{const i=process.platform==="win32";t=m(i?"cmd.exe":"npm",i?["/c","npm","root","-g"]:["root","-g"],{encoding:"utf-8",timeout:1e4}).trim()}catch{throw new s("VERSION_VERIFY_FAILED","Unable to resolve the global npm package directory")}let r;try{const i=f(t,"grix-connector","package.json"),n=JSON.parse(p(i,"utf-8"));if(n.name!=="grix-connector"||typeof n.version!="string"||!n.version.trim())throw new Error("invalid package metadata");r=n.version.trim().replace(/^v(?=\d)/,"")}catch{throw new s("VERSION_VERIFY_FAILED","Unable to read the installed grix-connector version")}if(r!==e)throw new s("VERSION_MISMATCH",`Installed version ${r} does not match expected ${e}`);return r}async function z(e,t){let r=!1;for(const i of L)try{const{stdout:n}=await b("npm",["view",`${e}@${t}`,"version","--registry",i.url,"--no-audit","--no-fund"],{timeout:8e3});if(n.trim())return!0}catch(n){const o=n,S=o.stderr||o.message||String(n);/ETARGET|No matching version|notarget|E404/i.test(S)&&(r=!0)}return!r}export{s as UpgradeError,_ as checkDiskSpace,h as checkNpmAvailable,B as collectEnvInfo,K as getUpgradeLogTail,z as isVersionAvailable,H as npmInstall,C as pendingExists,W as preflightCheck,j as readPending,J as removePending,u as upgradeLog,Y as verifyInstalledVersion,G as writePending};
@@ -1 +1 @@
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
+ function a(t){const o=new Set([`http://127.0.0.1:${t.serverPort}`,`http://localhost:${t.serverPort}`,...t.allowedOrigins]),e=new Set([`127.0.0.1:${t.serverPort}`,`localhost:${t.serverPort}`,...t.allowedHosts]);return{validateRequest(s){const r=i(s,o);if(!r.ok)return r;const n=l(s,e);return n.ok?{ok:!0}:n}}}function i(t,o){const e=t.headers.origin;return e?o.has(e)?{ok:!0}:{ok:!1,statusCode:403,message:`Origin not allowed: ${e}`}:{ok:!0}}function l(t,o){const e=t.headers.host;return e?o.has(e)?{ok:!0}:{ok:!1,statusCode:403,message:`Host not allowed: ${e}`}:{ok:!1,statusCode:403,message:"Missing Host header"}}export{a as createSecurityPolicy};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grix-connector",
3
- "version": "3.19.3",
3
+ "version": "3.19.5",
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",