grix-connector 4.11.13 → 4.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -47,11 +47,16 @@ The ACP adapter (Agent Client Protocol over JSON-RPC) backs Gemini, Qwen, Copilo
47
47
  "api_key": "your-grix-api-key",
48
48
  "client_type": "acp",
49
49
  "command": "my-cli",
50
- "args": ["acp"]
50
+ "args": ["acp"],
51
+ "cwd": "/srv/my-agent"
51
52
  }
52
53
  ```
53
54
 
54
- The generic entry ships the shared ACP surface (session binding, permission prompts, model/mode switching, file browsing). Per-agent extras — an installation guide, a dedicated toolbar, session usage parsing — still require a registered `client_type`.
55
+ The generic entry never asks the owner to pick a working directory. ACP's `session/new` requires a `cwd`, so the connector supplies one from the config: `cwd` if you set it, the home directory otherwise. It is fixed for the agent — `/grix open` and `/grix unbind` are rejected for `client_type: "acp"`, and the toolbar offers no binding entry. To move the agent, edit `cwd` and restart the connector. A `cwd` that does not exist is logged as an error and falls back to the home directory, so a typo cannot strand the agent. Every other `client_type` keeps the existing flow where the owner binds a directory per session.
56
+
57
+ The generic entry ships the shared ACP surface (permission prompts, model/mode switching, file browsing). Per-agent extras — an installation guide, a dedicated toolbar, session usage parsing — still require a registered `client_type`.
58
+
59
+ **[Connecting your own agent to Grix over ACP](docs/guide-acp-agent-integration.md)** is the full developer guide: asking the user a question and receiving the answer, what the toolbar can show and how the user's choice reaches you, the Grix MCP tools your agent gets for free, the card format, and the limits of the generic entry.
55
60
 
56
61
  Two limits of the generic entry are deliberate and will not be changed. Skill discovery for `client_type: "acp"` falls back to the Gemini layout, so it reads `~/.gemini/skills` and shares that directory with a real Gemini agent on the same machine. `get_session_usage` is not declared for it, because there is no parser for an unknown CLI's usage output. A CLI that needs its own skill directory or usage parsing must be registered as its own `client_type`.
57
62
 
@@ -262,6 +267,7 @@ Each agent entry uses one flat structure:
262
267
  | `pool.idleTimeoutMs` | no | Idle adapter eviction timeout (default 300000 = 5 min) |
263
268
  | `codex_config_overrides` | no | Codex-only app-server config overrides. The connector serializes this JSON object into validated `-c` flags. |
264
269
  | `command` | `acp` only | Executable to spawn for `client_type: "acp"`. Required for that type, ignored by every other type (their command is built in). |
270
+ | `cwd` | no | `acp` only: the fixed working directory passed to ACP `session/new`. Defaults to the home directory. The owner cannot change it from chat. |
265
271
  | `args` | no | Extra CLI arguments appended to the adapter's built-in ones. For `client_type: "acp"` these are the only arguments passed. |
266
272
 
267
273
  Adapter command/args/options are built in and resolved from `client_type`. To connect a different agent, simply change `client_type` — no other config changes needed. The one exception is `client_type: "acp"`, where the command comes from your config.
@@ -1,20 +1,20 @@
1
- import{fileURLToPath as X}from"node:url";import p from"node:path";import{homedir as g}from"node:os";import{promises as y,statSync as E}from"node:fs";import{EventEmitter as $}from"node:events";import{spawn as M}from"node:child_process";import{AgentProcess as V}from"../../agent/process.js";import{hasChildProcesses as Y,listGroupPids as J}from"../../core/runtime/spawn.js";import{applyNativeProviderConfig as Z,agentIdOf as C}from"../shared/native-provider-config.js";import{applyMcodeProviderConfig as ee}from"./mcode-provider-config.js";import{buildSessionContextBlock as te}from"../shared/session-context.js";import{IdentityInjector as ie}from"../shared/identity-injector.js";import{compactToolInputForWire as x}from"../shared/tool-wire-payload.js";import{syncDefaultSkillsToDir as se,resolveKimiCodeHome as ne}from"../../default-skills/index.js";import{resolveHermesHomeDir as oe}from"../shared/native-provider-config.js";import{prepareKimiLayerPlugin as re}from"../../kimi-plugins/index.js";import{AcpClient as ae,AcpAuthRequiredError as le,isAuthRequiredError as ce}from"../../protocol/acp-client.js";import{AgentEventType as f}from"../../types/events.js";import{InternalApiServer as de}from"../../core/mcp/internal-api-server.js";import{EventResultsStore as ue}from"../../core/persistence/event-results-store.js";import{QuotedMessageStream as S}from"../../core/util/quoted-message-stream.js";import{SafeMarkdownStreamSegmenter as he}from"../../core/text-segmentation/index.js";import{extractAcpTurnInput as pe}from"../../core/protocol/payload-parser.js";import{injectMessageMetadata as me}from"../../core/protocol/message-metadata.js";import{insertBeforeTrailingMessageReferences as w}from"../../core/protocol/message-reference.js";import{log as o}from"../../core/log/index.js";function q(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 fe=5e3;function N(c,e){const t=e.toolName??"";return e.toolCallId&&(!t||t===e.toolCallId)?c.toolNamesById.get(e.toolCallId)??t:t}function T(c){const e=String(c??"").trim();if(!e)return"";try{const t=JSON.parse(e);return JSON.stringify(_(t))}catch{return e}}function _(c){if(c===null||typeof c!="object")return c;if(Array.isArray(c))return c.map(_);const e=c,t=Object.keys(e).sort(),i={};for(const s of t)i[s]=_(e[s]);return i}function D(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 r=n.text;typeof r=="string"&&r.trim()&&t.push(r.trim())}}return t.join(`
2
- `)}function ge(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,D(s.content),s.content,s.description,i.tool_input,i.toolInput,e];for(const r of n){const a=x(r);if(a&&a!==t)return a}return""}const O=/^q\d+_opt_\d+$/,ve=/^q\d+_skip$/,Ie=new Set(["allow_once","allow_always","reject_once","reject_always"]);function ye(c){if(!`${c.toolName} ${c.toolTitle}`.toLowerCase().replace(/[^a-z]/g,"").includes("askuserquestion"))return null;const t=c.options;if(t.length===0)return null;const i=t.some(l=>O.test(l.optionId)),s=i?t.filter(l=>O.test(l.optionId)):t.filter(l=>!ve.test(l.optionId));if(s.length===0||!i&&s.every(l=>Ie.has(l.kind)))return null;const n=c.rawParams&&typeof c.rawParams=="object"?c.rawParams:{},r=n.toolCall&&typeof n.toolCall=="object"?n.toolCall:{},a=D(r.content).trim();return a?{prompt:a,answerOptions:s}:null}import{scanSkills as B}from"../claude/skill-scanner.js";import{checkKiroSessionActivity as Ae,checkKimiSessionActivity as Ce,checkQoderSessionActivity as F,checkDimSessionActivity as Se,hasRunningKimiDetachedTask as Te}from"./session-activity.js";import{resolveCliPath as be,getCliVersion as we}from"../../core/util/cli-probe.js";const R=p.dirname(X(import.meta.url)),U=[{modelId:"deepseek-v4-pro",name:"DeepSeek V4 Pro"},{modelId:"deepseek-v4-flash",name:"DeepSeek V4 Flash"}],k=[{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 P(c,e){const t=Number(process.env[c]);return Number.isFinite(t)&&t>0?t:e}const _e=200,Re=60*1e3,ke=600*1e3,b=P("GRIX_ACP_TURN_INACTIVITY_TIMEOUT_MS",900*1e3),j=2,L=5e3,K=80,W=1200*1e3,Pe=P("GRIX_ACP_BUSY_RETRY_INTERVAL_MS",5e3),H=P("GRIX_ACP_BUSY_WAIT_MAX_MS",1800*1e3),Ee=6e4,$e=2e3,Me=12e3,G=3e3,Q=3e4,xe=["[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 qe(c){return!(!(c instanceof Error)||c.code!==-32603||ce(c))}function Ne(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 A(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 De(c){return c.replace(/\u001b\[[0-9;?]*[ -/]*[@-~]/g,"")}class z extends ${type="acp";config;callbacks;agentProcess=null;acpClient=null;internalApi=null;activeRun=null;compacting=!1;compactingTimer=null;compactionDoneResolver=null;pendingAutoCompact=!1;eventResults=null;pendingApprovals=new Map;pendingQuestions=new Map;clientMsgSeq=0;stopped=!1;acpAuthMethod;acpInitialMode;acpInitialModel;newSessionParams;acpMcpTools;terminalAuthChild=null;rawTransport;approvalMode;autoInjectArgs;nativeProviderScope;reasonixAuditTranscriptPath;auditAgentType;trimToolWirePayload;activeAuditEventId=null;auditCaptures=new Map;completedAuditBoundaries=new Map;bindingStore;sessionBindings=new Map;deferredEvents=new Map;currentAibotSessionId;bridgeLog;sessionConnected=!1;sessionContextPending=!1;baselineGroupPids=[];rawEventSeq=0;recoveryContextBySessionId=new Map;quoteProtocolTaughtSessions=new Set;identity;orphanBuffer="";orphanQuotedStream=new S;orphanFlushTimer=null;orphanSessionId=null;orphanSuppressedUntil=0;constructor(e,t,i){super(),this.config=e,this.callbacks=t,this.identity=new ie("acp-adapter",t.getAgentProfile),this.bridgeLog=i?.bridgeLog??null,this.acpAuthMethod=i?.acpAuthMethod,this.acpInitialMode=i?.acpInitialMode,this.acpInitialModel=i?.acpInitialModel,this.newSessionParams=i?.newSessionParams,this.acpMcpTools=i?.acpMcpTools??!0,this.rawTransport=i?.rawTransport??!1,this.approvalMode=i?.approvalMode??"default",this.autoInjectArgs=i?.autoInjectArgs,this.nativeProviderScope=i?.nativeProviderScope,this.reasonixAuditTranscriptPath=i?.reasonixAuditTranscriptPath,this.auditAgentType=String(i?.agentType??p.basename(e.command)).trim().toLowerCase();const s=p.basename(e.command).toLowerCase(),n=this.auditAgentType==="copilot"||/^copilot(?:\.cmd)?$/i.test(s)||/^gh(?:\.exe)?$/i.test(s)&&(e.args??[]).includes("copilot");if(this.trimToolWirePayload=["kimi","kiro","qwen","reasonix","copilot"].includes(this.auditAgentType)||s.includes("kimi")||s==="kiro-cli"||/^qwen(?:\.cmd)?$/i.test(s)||s.includes("reasonix")||n,this.bindingStore=i?.bindingStore??null,this.currentAibotSessionId=i?.aibotSessionId?String(i.aibotSessionId).trim():void 0,i?.eventResultsPath&&(this.eventResults=new ue(i.eventResultsPath)),this.bindingStore&&this.currentAibotSessionId){const r=this.bindingStore.get(this.currentAibotSessionId);r?.cwd&&this.sessionBindings.set(this.currentAibotSessionId,r.cwd)}}async start(){if(await this.spawnProcess(),!this.bindingStore){await this.connectSession(this.resolveCwd());return}const e=this.currentAibotSessionId?this.sessionBindings.get(this.currentAibotSessionId):void 0;e&&await this.connectSession(e)}async stop(){this.stopped=!0,this.rejectDeferredEvents("adapter stopped"),this.cancelAllDeferredTimers(),this.pendingAutoCompact=!1,this.compacting=!1,this.compactingTimer&&(clearTimeout(this.compactingTimer),this.compactingTimer=null),this.compactionDoneResolver&&(this.compactionDoneResolver(),this.compactionDoneResolver=null),this.activeRun&&this.finishRun("canceled","adapter stopped"),this.flushOrphanBuffer(),this.acpClient&&(this.acpClient.dispose(),this.acpClient.removeAllListeners(),this.acpClient=null),this.agentProcess&&(await this.agentProcess.close(),this.agentProcess=null),this.baselineGroupPids=[],this.internalApi&&(await this.internalApi.stop(),this.internalApi=null),this.terminalAuthChild&&(this.terminalAuthChild.kill("SIGKILL"),this.terminalAuthChild=null)}isAlive(){return this.agentProcess?.alive??!1}async createSession(e){return this.acpClient?.sessionId??""}async resumeSession(e,t){}async destroySession(e){this.quoteProtocolTaughtSessions.delete(e),this.identity.forgetSession(e)}onAgentProfileChanged(){this.identity.onProfileChanged()}sendPrompt(e){const t=new Oe(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(Q))}setPermissionHandler(e){}async ping(e){return this.acpClient?.isAlive?this.acpClient.ping(e):this.agentProcess?.alive??!1}getStatus(){return{alive:this.agentProcess?.alive??!1,busy:this.activeRun!==null||this.compacting,sessions:this.sessionConnected?1:0}}getActiveEventIds(){return this.activeRun?[this.activeRun.eventId]:[]}clearActiveEventForShutdown(){this.activeRun&&(this.snapshotAuditBoundary(this.activeRun.eventId),this.clearRunTimers(this.activeRun),this.activeRun=null)}takeAuditBoundary(e){const t=this.completedAuditBoundaries.get(e);return t&&this.completedAuditBoundaries.delete(e),t}clearRunTimers(e){e.flushTimer&&(clearTimeout(e.flushTimer),e.flushTimer=null),e.idleTimer&&(clearTimeout(e.idleTimer),e.idleTimer=null),e.busyRetryTimer&&(clearTimeout(e.busyRetryTimer),e.busyRetryTimer=null)}getMcpConfig(){if(!this.internalApi)return null;this.internalApi.setMcpBridgeUpHandler(t=>this.onMcpBridgeUp(t));const e=p.resolve(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?Y(e,[e,...this.baselineGroupPids]):!1}captureBaselineGroupPids(){const e=this.agentProcess?.pid;e&&J(e).then(t=>{this.baselineGroupPids=t}).catch(()=>{})}async probe(e){const t=this.config.command||"gemini",i=await be(t),s=i!==null;let n=null,r;if(s){const h=await we(t);n=h.version,h.error&&(r=h.error)}else r={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,...r?{error:r}:{}},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=B({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?U:this.isKimi?k: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 r=i?.modes.map(m=>({id:m.id,name:m.name}))??[],a=i?.models&&i.models.length>0,l=a?i.models:this.isReasonix?U:this.isKimi?k:[],d=a?"acp":this.isReasonix?"reasonix_fallback":this.isKimi?"kimi_fallback":"empty";this.isKimi&&!a&&o.warn("acp-adapter",`[toolbar-diag] kimi ACP session returned no models; using static fallback. command=${this.config.command} fallbackIds=${JSON.stringify(k.map(m=>m.modelId))}`),o.info("acp-adapter",`[toolbar-diag] buildToolbarContext outcome=${e} agent=${this.config.command} modelSource=${d} acpModelCount=${i?.models?.length??0} finalModelCount=${l.length}`);const h=l.map(m=>({modelId:m.modelId,name:m.name})),u=l.map(m=>({id:m.modelId,displayName:m.name})),I=i?.modes.map(m=>({id:m.id,displayName:m.name}))??[];u.length>0&&!u.some(m=>m.id===n)&&(n=this.resolveFallbackCurrentModelId(l,n));const v={outcome:e,...t?{cwd:t}:{},model_id:n,mode_id:s,currentModelId:n,currentModeId:s,available_models:u,available_modes:I,availableModels:u,availableModes:I,models:h,modes:r};return o.info("acp-adapter",`[toolbar] buildToolbarContext outcome=${e} model_id="${n}" available_models=${JSON.stringify(u.map(m=>m.id))} currentModelId=${n}`),v}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?(o.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=>{o.error("acp-adapter",`Failed to respond to permission: ${s}`)}),this.captureAuditEvent({type:f.PermissionResponse,requestId:i,toolCallId:e,permissionDecision:t}),this.activeRun&&(this.activeRun.lastProgressAt=Date.now(),this.resetIdleTimer(this.activeRun)),!0):(o.warn("acp-adapter","handleAcpApprovalAction: no pending approval",{toolCallId:e,behavior:t,pendingCount:this.pendingApprovals.size}),!1)}handleExecApprovalEvent(e){const t=String(e.tool_call_id??e.approval_command_id??e.approval_id??e.exec_context_id??"");return t?this.handleAcpApprovalAction(t,e.decision)?{delivered:!0}:{delivered:!1,errorCode:"approval_not_found",errorMsg:`no pending approval for tool_call_id: ${t}`}:{delivered:!1,errorCode:"approval_id_required",errorMsg:"approval id is required"}}respondToPermission(e,t){this.acpClient&&this.acpClient.respondPermission(e,t).catch(i=>{o.error("acp-adapter",`Failed to respond to permission: ${i}`)}),this.captureAuditEvent({type:f.PermissionResponse,requestId:e,permissionDecision:t.behavior}),this.activeRun&&(this.activeRun.lastProgressAt=Date.now(),this.resetIdleTimer(this.activeRun))}async handleLocalAction(e){const t=e.action_type??"",i=e.params??{};if(t==="claude_interaction_reply"&&String(i.kind??"")==="permission"){const s=i.resolution&&typeof i.resolution=="object"?i.resolution:{},n=String(s.value??i.decision??"");return["allow","allow-once","allow-always","deny"].includes(n)?this.handleLocalAction({...e,action_type:n==="deny"?"permission_reject":"permission_approve",params:{...i,approval_command_id:i.request_id,decision:n}}):(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"permission_decision_invalid","permission decision is invalid"),{handled:!0,kind:"approval"})}if(t==="claude_interaction_reply")return this.handleAcpQuestionReply(e);if(t==="exec_approve"||t==="exec_reject"||t==="permission_approve"||t==="permission_reject"){const s=String(i.tool_call_id??i.approval_command_id??i.approval_id??i.exec_context_id??""),n=t==="exec_approve"||t==="permission_approve",r=String(i.decision??"");let a;return n&&(r==="allow-once"||r==="allow-always")?a=r:n?a="allow":a="deny",s?this.handleAcpApprovalAction(s,a)?(this.callbacks.sendLocalActionResult(e.action_id,"ok"),{handled:!0,kind:"approval"}):(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"approval_not_found",`no pending approval for tool_call_id: ${s}`),{handled:!0,kind:"approval"}):(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"tool_call_id_required","tool_call_id is required"),{handled:!0,kind:"approval"})}return{handled:!1,kind:""}}async handleQuestionReplyEvent(e){let t;if(e.action)t={type:"action",value:e.action};else if(e.response?.type==="single")t={type:"text",value:e.response.value};else if(e.response?.type==="map")t={type:"map",entries:e.response.entries};else return{delivered:!1,errorCode:"resolution_invalid",errorMsg:"Question reply response is invalid."};return this.deliverQuestionReply({request_id:e.request_id,resolution:t})}async handleAcpQuestionReply(e){const t=await this.deliverQuestionReply(e.params??{});return t.delivered?(this.callbacks.sendLocalActionResult(e.action_id,"ok",t.result),{handled:!0,kind:t.kind??"question_reply"}):(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,t.errorCode,t.errorMsg),{handled:!0,kind:t.kind??"question_reply_failed"})}async deliverQuestionReply(e){const t=String(e.request_id??"").trim(),i=this.pendingQuestions.get(t);if(!t||!i||!this.acpClient)return{delivered:!1,errorCode:"interaction_request_not_pending",errorMsg:"The question is no longer pending; the reply was not delivered.",kind:"question_reply_not_pending"};const s=e.resolution&&typeof e.resolution=="object"?e.resolution:{},n=String(s.type??"");let r;if(n==="action"&&String(s.value??"")==="cancel")r=i.options.find(l=>/_skip$/.test(l.optionId)||l.kind.toLowerCase().includes("reject"));else{let l="";if(n==="text")l=String(s.value??"").trim();else if(n==="map"){const d=Array.isArray(s.entries)?s.entries:[];l=String(d[0]?.value??"").trim()}r=i.options.find(d=>d.name.trim()===l||d.optionId===l)}if(!r)return{delivered:!1,errorCode:"question_answer_invalid",errorMsg:"The selected answer does not match a pending question option. The question is still open; please tap one of the options on the card (free-text answers are not supported).",kind:"question_reply_invalid"};try{await this.acpClient.respondPermissionOption(t,r.optionId)}catch(l){return{delivered:!1,errorCode:"question_reply_failed",errorMsg:l instanceof Error?l.message:String(l),kind:"question_reply_failed"}}this.pendingQuestions.delete(t);const a=/_skip$/.test(r.optionId);return this.captureAuditEvent({type:f.PermissionResponse,requestId:t,permissionDecision:a?"deny":"allow"}),this.activeRun&&(this.activeRun.lastProgressAt=Date.now(),this.resetIdleTimer(this.activeRun)),{delivered:!0,result:{request_id:t,resolution:a?"cancel":"answer"},kind:a?"question_reply_cancel":"question_reply"}}resolveCwd(){if(this.currentAibotSessionId){const e=this.sessionBindings.get(this.currentAibotSessionId);if(e)return e}if(this.bindingStore&&this.currentAibotSessionId){const e=this.bindingStore.get(this.currentAibotSessionId);if(e?.cwd)return e.cwd}return process.cwd()}async bindSession(e,t){const i=this.sessionBindings.get(e);if(i)try{return await y.stat(i),!1}catch{o.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=>{o.error("acp-adapter",`Failed to create session on bind: ${A(s)}`);const n=this.buildToolbarContext("bind_failed",t),r=Array.isArray(n.available_models)&&n.available_models.length>0;return this.callbacks.sendUpdateBindingCard(e,"failed",t,r?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=B({mode:"kiro",projectDir:t})}catch(s){o.warn("acp-adapter",`Kiro skills scan failed: ${s instanceof Error?s.message:String(s)}`);return}try{this.callbacks.onSkillsUpdate(i,t)}catch(s){o.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){o.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),o.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(Q),o.info("acp-adapter",`Compaction finished (${e}); replaying deferred events`),this.replayNextDeferredEvent();const t=this.compactionDoneResolver;this.compactionDoneResolver=null,t?.()}replayNextDeferredEvent(){if(this.activeRun||this.compacting)return;const e=this.deferredEvents.keys().next().value;e&&this.replayDeferredEvents(e)}announceDeferredComposing(e){}sendTerminalEventWithoutRun(e,t,i){this.emit("eventDone",e),this.callbacks.sendEventResult(e,t,i)}deliverInboundEvent(e){if(this.eventResults?.has(e.session_id,e.event_id)){const t=this.eventResults.get(e.session_id,e.event_id);o.info("acp-adapter",`Deduplicating event ${e.event_id} (cached: ${t.status})`),this.sendTerminalEventWithoutRun(e.event_id,t.status,t.msg);return}if(this.compacting){o.info("acp-adapter",`Event ${e.event_id} deferred: compaction in progress`),this.deferEvent(e);return}if(this.activeRun){o.info("acp-adapter",`Event ${e.event_id} rejected: busy`),this.sendTerminalEventWithoutRun(e.event_id,"failed","agent busy");return}if(!this.agentProcess?.alive){o.warn("acp-adapter",`Event ${e.event_id} cannot start: agent process not alive; requesting session recovery`),this.emit("internalError",{eventId:e.event_id,sessionId:e.session_id,errorMsg:"agent not alive",replayOriginalContent:!0});return}e.session_id&&e.session_id!==this.currentAibotSessionId&&(this.currentAibotSessionId=e.session_id),this.startRun(e,!1)}deliverStopEvent(e,t){this.acpClient?(this.acpClient.cancel().catch(()=>{}),this.flushStream()):this.flushStream(),this.activeRun?.eventId===e&&this.finishRun("canceled","stopped by user")}deferEvent(e){const t=this.deferredEvents.get(e.session_id)??[];t.some(i=>i.event.event_id===e.event_id)||(t.push({event:e,queuedAt:Date.now()}),this.deferredEvents.set(e.session_id,t),o.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)o.info("acp-adapter",`Rejecting deferred event ${s.event_id}: ${e}`),this.sendTerminalEventWithoutRun(s.event_id,"failed",e);this.deferredEvents.clear(),this.cancelAllDeferredTimers()}cancelDeferredTimer(e){const t=this.deferredTimers.get(e);t&&(clearTimeout(t),this.deferredTimers.delete(e))}cancelAllDeferredTimers(){for(const e of this.deferredTimers.values())clearTimeout(e);this.deferredTimers.clear()}scheduleDeferredTimeout(e){if(this.deferredTimers.has(e))return;const t=3e4,i=setTimeout(()=>{this.deferredTimers.delete(e);const s=this.deferredEvents.get(e);if(!(!s||s.length===0)&&!this.acpClient?.isAlive){o.error("acp-adapter",`Deferred events for session ${e} timed out after ${t/1e3}s (no ACP client)`),this.deferredEvents.delete(e);for(const{event:n}of s)this.sendTerminalEventWithoutRun(n.event_id,"failed","agent initialization timed out")}},t);this.deferredTimers.set(e,i)}startRun(e,t){if(!this.acpClient?.isAlive){this.deferEvent(e);return}const i=`acp_${++this.clientMsgSeq}_${Date.now()}`;this.activeRun={eventId:e.event_id,sessionId:e.session_id,threadId:e.thread_id,clientMsgIdBase:i,currentClientMsgId:i,currentSegmentIndex:0,chunkSeq:0,buffer:"",quotedStream:new S,markdownSegmenter:new he,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=pe(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 r=me(n.prompt,{messageId:e.msg_id,quotedMessageId:e.quoted_message_id}),a=this.injectQuoteProtocolOnce(s.sessionId,r),l=this.identity.injectOnce(s.sessionId,a);if(this.bindingStore&&s.sessionId){const u=this.callbacks.getAgentProfile?.();this.bindingStore.setIdentitySnapshot(s.sessionId,u?.agentName?.trim()??"",u?.introduction?.trim()??"")}const d=this.injectSessionContextOnce(s.sessionId,l),h=this.injectRecoveryContext(s.sessionId,d);this.sendPromptWithRetry(s,h)}injectQuoteProtocolOnce(e,t){return!e||this.quoteProtocolTaughtSessions.has(e)?t:(this.quoteProtocolTaughtSessions.add(e),w(t,xe))}injectSessionContextOnce(e,t){return!e||!this.sessionContextPending?t:(this.sessionContextPending=!1,o.info("acp-adapter",`Injected Grix session context for session=${e}`),w(t,te(e,this.callbacks.getAgentId?.())))}injectRecoveryContext(e,t){const i=this.recoveryContextBySessionId.get(e);return i?(this.recoveryContextBySessionId.delete(e),o.info("acp-adapter",`Injecting recovery context for session ${e} (chars=${i.length})`),w(t,`[recovery-context]
1
+ import{fileURLToPath as X}from"node:url";import p from"node:path";import{homedir as g}from"node:os";import{promises as y,statSync as E}from"node:fs";import{EventEmitter as $}from"node:events";import{spawn as M}from"node:child_process";import{AgentProcess as V}from"../../agent/process.js";import{hasChildProcesses as Y,listGroupPids as J}from"../../core/runtime/spawn.js";import{applyNativeProviderConfig as Z,agentIdOf as C}from"../shared/native-provider-config.js";import{applyMcodeProviderConfig as ee}from"./mcode-provider-config.js";import{buildSessionContextBlock as te}from"../shared/session-context.js";import{IdentityInjector as ie}from"../shared/identity-injector.js";import{compactToolInputForWire as x}from"../shared/tool-wire-payload.js";import{syncDefaultSkillsToDir as se,resolveKimiCodeHome as ne}from"../../default-skills/index.js";import{resolveHermesHomeDir as oe}from"../shared/native-provider-config.js";import{prepareKimiLayerPlugin as re}from"../../kimi-plugins/index.js";import{AcpClient as ae,AcpAuthRequiredError as le,isAuthRequiredError as ce}from"../../protocol/acp-client.js";import{AgentEventType as f}from"../../types/events.js";import{InternalApiServer as de}from"../../core/mcp/internal-api-server.js";import{EventResultsStore as ue}from"../../core/persistence/event-results-store.js";import{QuotedMessageStream as S}from"../../core/util/quoted-message-stream.js";import{SafeMarkdownStreamSegmenter as he}from"../../core/text-segmentation/index.js";import{extractAcpTurnInput as pe}from"../../core/protocol/payload-parser.js";import{injectMessageMetadata as me}from"../../core/protocol/message-metadata.js";import{insertBeforeTrailingMessageReferences as _}from"../../core/protocol/message-reference.js";import{log as r}from"../../core/log/index.js";function q(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 fe=5e3;function N(c,e){const t=e.toolName??"";return e.toolCallId&&(!t||t===e.toolCallId)?c.toolNamesById.get(e.toolCallId)??t:t}function b(c){const e=String(c??"").trim();if(!e)return"";try{const t=JSON.parse(e);return JSON.stringify(w(t))}catch{return e}}function w(c){if(c===null||typeof c!="object")return c;if(Array.isArray(c))return c.map(w);const e=c,t=Object.keys(e).sort(),i={};for(const s of t)i[s]=w(e[s]);return i}function D(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 a=n.text;typeof a=="string"&&a.trim()&&t.push(a.trim())}}return t.join(`
2
+ `)}function ge(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,D(s.content),s.content,s.description,i.tool_input,i.toolInput,e];for(const a of n){const o=x(a);if(o&&o!==t)return o}return""}const O=/^q\d+_opt_\d+$/,ve=/^q\d+_skip$/,Ie=new Set(["allow_once","allow_always","reject_once","reject_always"]);function ye(c){if(!`${c.toolName} ${c.toolTitle}`.toLowerCase().replace(/[^a-z]/g,"").includes("askuserquestion"))return null;const t=c.options;if(t.length===0)return null;const i=t.some(l=>O.test(l.optionId)),s=i?t.filter(l=>O.test(l.optionId)):t.filter(l=>!ve.test(l.optionId));if(s.length===0||!i&&s.every(l=>Ie.has(l.kind)))return null;const n=c.rawParams&&typeof c.rawParams=="object"?c.rawParams:{},a=n.toolCall&&typeof n.toolCall=="object"?n.toolCall:{},o=D(a.content).trim();return o?{prompt:o,answerOptions:s}:null}import{scanSkills as B}from"../claude/skill-scanner.js";import{checkKiroSessionActivity as Ae,checkKimiSessionActivity as Ce,checkQoderSessionActivity as F,checkDimSessionActivity as Se,hasRunningKimiDetachedTask as be}from"./session-activity.js";import{resolveCliPath as Te,getCliVersion as _e}from"../../core/util/cli-probe.js";const R=p.dirname(X(import.meta.url)),U=[{modelId:"deepseek-v4-pro",name:"DeepSeek V4 Pro"},{modelId:"deepseek-v4-flash",name:"DeepSeek V4 Flash"}],k=[{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 P(c,e){const t=Number(process.env[c]);return Number.isFinite(t)&&t>0?t:e}const we=200,Re=60*1e3,ke=600*1e3,T=P("GRIX_ACP_TURN_INACTIVITY_TIMEOUT_MS",900*1e3),j=2,L=5e3,K=80,W=1200*1e3,Pe=P("GRIX_ACP_BUSY_RETRY_INTERVAL_MS",5e3),H=P("GRIX_ACP_BUSY_WAIT_MAX_MS",1800*1e3),Ee=6e4,$e=2e3,Me=12e3,G=3e3,Q=3e4,xe=["[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 qe(c){return!(!(c instanceof Error)||c.code!==-32603||ce(c))}function Ne(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 A(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 De(c){return c.replace(/\u001b\[[0-9;?]*[ -/]*[@-~]/g,"")}class z extends ${type="acp";config;callbacks;agentProcess=null;acpClient=null;internalApi=null;activeRun=null;compacting=!1;compactingTimer=null;compactionDoneResolver=null;pendingAutoCompact=!1;eventResults=null;pendingApprovals=new Map;pendingQuestions=new Map;clientMsgSeq=0;stopped=!1;acpAuthMethod;acpInitialMode;acpInitialModel;newSessionParams;acpMcpTools;terminalAuthChild=null;rawTransport;approvalMode;autoInjectArgs;nativeProviderScope;reasonixAuditTranscriptPath;auditAgentType;trimToolWirePayload;activeAuditEventId=null;auditCaptures=new Map;completedAuditBoundaries=new Map;bindingStore;sessionBindings=new Map;deferredEvents=new Map;currentAibotSessionId;bridgeLog;sessionConnected=!1;sessionContextPending=!1;baselineGroupPids=[];rawEventSeq=0;recoveryContextBySessionId=new Map;quoteProtocolTaughtSessions=new Set;identity;orphanBuffer="";orphanQuotedStream=new S;orphanFlushTimer=null;orphanSessionId=null;orphanSuppressedUntil=0;constructor(e,t,i){super(),this.config=e,this.callbacks=t,this.identity=new ie("acp-adapter",t.getAgentProfile),this.bridgeLog=i?.bridgeLog??null,this.acpAuthMethod=i?.acpAuthMethod,this.acpInitialMode=i?.acpInitialMode,this.acpInitialModel=i?.acpInitialModel,this.newSessionParams=i?.newSessionParams,this.acpMcpTools=i?.acpMcpTools??!0,this.rawTransport=i?.rawTransport??!1,this.approvalMode=i?.approvalMode??"default",this.autoInjectArgs=i?.autoInjectArgs,this.nativeProviderScope=i?.nativeProviderScope,this.reasonixAuditTranscriptPath=i?.reasonixAuditTranscriptPath,this.auditAgentType=String(i?.agentType??p.basename(e.command)).trim().toLowerCase();const s=p.basename(e.command).toLowerCase(),n=this.auditAgentType==="copilot"||/^copilot(?:\.cmd)?$/i.test(s)||/^gh(?:\.exe)?$/i.test(s)&&(e.args??[]).includes("copilot");if(this.trimToolWirePayload=["kimi","kiro","qwen","reasonix","copilot"].includes(this.auditAgentType)||s.includes("kimi")||s==="kiro-cli"||/^qwen(?:\.cmd)?$/i.test(s)||s.includes("reasonix")||n,this.bindingStore=i?.bindingStore??null,this.currentAibotSessionId=i?.aibotSessionId?String(i.aibotSessionId).trim():void 0,i?.eventResultsPath&&(this.eventResults=new ue(i.eventResultsPath)),this.bindingStore&&this.currentAibotSessionId){const a=this.bindingStore.get(this.currentAibotSessionId);a?.cwd&&this.sessionBindings.set(this.currentAibotSessionId,a.cwd)}}async start(){if(await this.spawnProcess(),!this.bindingStore){await this.connectSession(this.resolveCwd());return}const e=this.currentAibotSessionId?this.sessionBindings.get(this.currentAibotSessionId):void 0;e&&await this.connectSession(e)}async stop(){this.stopped=!0,this.rejectDeferredEvents("adapter stopped"),this.cancelAllDeferredTimers(),this.pendingAutoCompact=!1,this.compacting=!1,this.compactingTimer&&(clearTimeout(this.compactingTimer),this.compactingTimer=null),this.compactionDoneResolver&&(this.compactionDoneResolver(),this.compactionDoneResolver=null),this.activeRun&&this.finishRun("canceled","adapter stopped"),this.flushOrphanBuffer(),this.acpClient&&(this.acpClient.dispose(),this.acpClient.removeAllListeners(),this.acpClient=null),this.agentProcess&&(await this.agentProcess.close(),this.agentProcess=null),this.baselineGroupPids=[],this.internalApi&&(await this.internalApi.stop(),this.internalApi=null),this.terminalAuthChild&&(this.terminalAuthChild.kill("SIGKILL"),this.terminalAuthChild=null)}isAlive(){return this.agentProcess?.alive??!1}async createSession(e){return this.acpClient?.sessionId??""}async resumeSession(e,t){}async destroySession(e){this.quoteProtocolTaughtSessions.delete(e),this.identity.forgetSession(e)}onAgentProfileChanged(){this.identity.onProfileChanged()}sendPrompt(e){const t=new Oe(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(Q))}setPermissionHandler(e){}async ping(e){return this.acpClient?.isAlive?this.acpClient.ping(e):this.agentProcess?.alive??!1}getStatus(){return{alive:this.agentProcess?.alive??!1,busy:this.activeRun!==null||this.compacting,sessions:this.sessionConnected?1:0}}getActiveEventIds(){return this.activeRun?[this.activeRun.eventId]:[]}clearActiveEventForShutdown(){this.activeRun&&(this.snapshotAuditBoundary(this.activeRun.eventId),this.clearRunTimers(this.activeRun),this.activeRun=null)}takeAuditBoundary(e){const t=this.completedAuditBoundaries.get(e);return t&&this.completedAuditBoundaries.delete(e),t}clearRunTimers(e){e.flushTimer&&(clearTimeout(e.flushTimer),e.flushTimer=null),e.idleTimer&&(clearTimeout(e.idleTimer),e.idleTimer=null),e.busyRetryTimer&&(clearTimeout(e.busyRetryTimer),e.busyRetryTimer=null)}getMcpConfig(){if(!this.internalApi)return null;this.internalApi.setMcpBridgeUpHandler(t=>this.onMcpBridgeUp(t));const e=p.resolve(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?Y(e,[e,...this.baselineGroupPids]):!1}captureBaselineGroupPids(){const e=this.agentProcess?.pid;e&&J(e).then(t=>{this.baselineGroupPids=t}).catch(()=>{})}async probe(e){const t=this.config.command||"gemini",i=await Te(t),s=i!==null;let n=null,a;if(s){const h=await _e(t);n=h.version,h.error&&(a=h.error)}else a={code:"cli_not_found",message:`command not found: ${t}`};const o=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,...a?{error:a}:{}},conversation:d,config:{model:o,base_url:null,source:{model:o?"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=B({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?U:this.isKimi?k: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 a=i?.modes.map(m=>({id:m.id,name:m.name}))??[],o=i?.models&&i.models.length>0,l=o?i.models:this.isReasonix?U:this.isKimi?k:[],d=o?"acp":this.isReasonix?"reasonix_fallback":this.isKimi?"kimi_fallback":"empty";this.isKimi&&!o&&r.warn("acp-adapter",`[toolbar-diag] kimi ACP session returned no models; using static fallback. command=${this.config.command} fallbackIds=${JSON.stringify(k.map(m=>m.modelId))}`),r.info("acp-adapter",`[toolbar-diag] buildToolbarContext outcome=${e} agent=${this.config.command} modelSource=${d} acpModelCount=${i?.models?.length??0} finalModelCount=${l.length}`);const h=l.map(m=>({modelId:m.modelId,name:m.name})),u=l.map(m=>({id:m.modelId,displayName:m.name})),I=i?.modes.map(m=>({id:m.id,displayName:m.name}))??[];u.length>0&&!u.some(m=>m.id===n)&&(n=this.resolveFallbackCurrentModelId(l,n));const v={outcome:e,...t?{cwd:t}:{},model_id:n,mode_id:s,currentModelId:n,currentModeId:s,available_models:u,available_modes:I,availableModels:u,availableModes:I,models:h,modes:a};return r.info("acp-adapter",`[toolbar] buildToolbarContext outcome=${e} model_id="${n}" available_models=${JSON.stringify(u.map(m=>m.id))} currentModelId=${n}`),v}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.captureAuditEvent({type:f.PermissionResponse,requestId:i,toolCallId:e,permissionDecision:t}),this.activeRun&&(this.activeRun.lastProgressAt=Date.now(),this.resetIdleTimer(this.activeRun)),!0):(r.warn("acp-adapter","handleAcpApprovalAction: no pending approval",{toolCallId:e,behavior:t,pendingCount:this.pendingApprovals.size}),!1)}handleExecApprovalEvent(e){const t=String(e.tool_call_id??e.approval_command_id??e.approval_id??e.exec_context_id??"");return t?this.handleAcpApprovalAction(t,e.decision)?{delivered:!0}:{delivered:!1,errorCode:"approval_not_found",errorMsg:`no pending approval for tool_call_id: ${t}`}:{delivered:!1,errorCode:"approval_id_required",errorMsg:"approval id is required"}}respondToPermission(e,t){this.acpClient&&this.acpClient.respondPermission(e,t).catch(i=>{r.error("acp-adapter",`Failed to respond to permission: ${i}`)}),this.captureAuditEvent({type:f.PermissionResponse,requestId:e,permissionDecision:t.behavior}),this.activeRun&&(this.activeRun.lastProgressAt=Date.now(),this.resetIdleTimer(this.activeRun))}async handleLocalAction(e){const t=e.action_type??"",i=e.params??{};if(t==="claude_interaction_reply"&&String(i.kind??"")==="permission"){const s=i.resolution&&typeof i.resolution=="object"?i.resolution:{},n=String(s.value??i.decision??"");return["allow","allow-once","allow-always","deny"].includes(n)?this.handleLocalAction({...e,action_type:n==="deny"?"permission_reject":"permission_approve",params:{...i,approval_command_id:i.request_id,decision:n}}):(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"permission_decision_invalid","permission decision is invalid"),{handled:!0,kind:"approval"})}if(t==="claude_interaction_reply")return this.handleAcpQuestionReply(e);if(t==="exec_approve"||t==="exec_reject"||t==="permission_approve"||t==="permission_reject"){const s=String(i.tool_call_id??i.approval_command_id??i.approval_id??i.exec_context_id??""),n=t==="exec_approve"||t==="permission_approve",a=String(i.decision??"");let o;return n&&(a==="allow-once"||a==="allow-always")?o=a:n?o="allow":o="deny",s?this.handleAcpApprovalAction(s,o)?(this.callbacks.sendLocalActionResult(e.action_id,"ok"),{handled:!0,kind:"approval"}):(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"approval_not_found",`no pending approval for tool_call_id: ${s}`),{handled:!0,kind:"approval"}):(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"tool_call_id_required","tool_call_id is required"),{handled:!0,kind:"approval"})}return{handled:!1,kind:""}}async handleQuestionReplyEvent(e){let t;if(e.action)t={type:"action",value:e.action};else if(e.response?.type==="single")t={type:"text",value:e.response.value};else if(e.response?.type==="map")t={type:"map",entries:e.response.entries};else return{delivered:!1,errorCode:"resolution_invalid",errorMsg:"Question reply response is invalid."};return this.deliverQuestionReply({request_id:e.request_id,resolution:t})}async handleAcpQuestionReply(e){const t=await this.deliverQuestionReply(e.params??{});return t.delivered?(this.callbacks.sendLocalActionResult(e.action_id,"ok",t.result),{handled:!0,kind:t.kind??"question_reply"}):(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,t.errorCode,t.errorMsg),{handled:!0,kind:t.kind??"question_reply_failed"})}async deliverQuestionReply(e){const t=String(e.request_id??"").trim(),i=this.pendingQuestions.get(t);if(!t||!i||!this.acpClient)return{delivered:!1,errorCode:"interaction_request_not_pending",errorMsg:"The question is no longer pending; the reply was not delivered.",kind:"question_reply_not_pending"};const s=e.resolution&&typeof e.resolution=="object"?e.resolution:{},n=String(s.type??"");let a;if(n==="action"&&String(s.value??"")==="cancel")a=i.options.find(l=>/_skip$/.test(l.optionId)||l.kind.toLowerCase().includes("reject"));else{let l="";if(n==="text")l=String(s.value??"").trim();else if(n==="map"){const d=Array.isArray(s.entries)?s.entries:[];l=String(d[0]?.value??"").trim()}a=i.options.find(d=>d.name.trim()===l||d.optionId===l)}if(!a)return{delivered:!1,errorCode:"question_answer_invalid",errorMsg:"The selected answer does not match a pending question option. The question is still open; please tap one of the options on the card (free-text answers are not supported).",kind:"question_reply_invalid"};try{await this.acpClient.respondPermissionOption(t,a.optionId)}catch(l){return{delivered:!1,errorCode:"question_reply_failed",errorMsg:l instanceof Error?l.message:String(l),kind:"question_reply_failed"}}this.pendingQuestions.delete(t);const o=/_skip$/.test(a.optionId);return this.captureAuditEvent({type:f.PermissionResponse,requestId:t,permissionDecision:o?"deny":"allow"}),this.activeRun&&(this.activeRun.lastProgressAt=Date.now(),this.resetIdleTimer(this.activeRun)),{delivered:!0,result:{request_id:t,resolution:o?"cancel":"answer"},kind:o?"question_reply_cancel":"question_reply"}}resolveCwd(){if(this.currentAibotSessionId){const e=this.sessionBindings.get(this.currentAibotSessionId);if(e)return e}if(this.bindingStore&&this.currentAibotSessionId){const e=this.bindingStore.get(this.currentAibotSessionId);if(e?.cwd)return e.cwd}return process.cwd()}async bindSession(e,t){const i=this.sessionBindings.get(e);if(i)try{return await y.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: ${A(s)}`);const n=this.buildToolbarContext("bind_failed",t),a=Array.isArray(n.available_models)&&n.available_models.length>0;return this.callbacks.sendUpdateBindingCard(e,"failed",t,a?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=B({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(Q),r.info("acp-adapter",`Compaction finished (${e}); replaying deferred events`),this.replayNextDeferredEvent();const t=this.compactionDoneResolver;this.compactionDoneResolver=null,t?.()}replayNextDeferredEvent(){if(this.activeRun||this.compacting)return;const e=this.deferredEvents.keys().next().value;e&&this.replayDeferredEvents(e)}announceDeferredComposing(e){}sendTerminalEventWithoutRun(e,t,i){this.emit("eventDone",e),this.callbacks.sendEventResult(e,t,i)}deliverInboundEvent(e){if(this.eventResults?.has(e.session_id,e.event_id)){const t=this.eventResults.get(e.session_id,e.event_id);r.info("acp-adapter",`Deduplicating event ${e.event_id} (cached: ${t.status})`),this.sendTerminalEventWithoutRun(e.event_id,t.status,t.msg);return}if(this.compacting){r.info("acp-adapter",`Event ${e.event_id} deferred: compaction in progress`),this.deferEvent(e);return}if(this.activeRun){r.info("acp-adapter",`Event ${e.event_id} rejected: busy`),this.sendTerminalEventWithoutRun(e.event_id,"failed","agent busy");return}if(!this.agentProcess?.alive){r.warn("acp-adapter",`Event ${e.event_id} cannot start: agent process not alive; requesting session recovery`),this.emit("internalError",{eventId:e.event_id,sessionId:e.session_id,errorMsg:"agent not alive",replayOriginalContent:!0});return}e.session_id&&e.session_id!==this.currentAibotSessionId&&(this.currentAibotSessionId=e.session_id),this.startRun(e,!1)}deliverStopEvent(e,t){this.acpClient?(this.acpClient.cancel().catch(()=>{}),this.flushStream()):this.flushStream(),this.activeRun?.eventId===e&&this.finishRun("canceled","stopped by user")}deferEvent(e){const t=this.deferredEvents.get(e.session_id)??[];t.some(i=>i.event.event_id===e.event_id)||(t.push({event:e,queuedAt:Date.now()}),this.deferredEvents.set(e.session_id,t),r.info("acp-adapter",`Deferred event ${e.event_id} for session ${e.session_id} (queue: ${t.length})`),this.scheduleDeferredTimeout(e.session_id))}deferredTimers=new Map;rejectDeferredEvents(e){for(const[t,i]of this.deferredEvents)for(const{event:s}of i)r.info("acp-adapter",`Rejecting deferred event ${s.event_id}: ${e}`),this.sendTerminalEventWithoutRun(s.event_id,"failed",e);this.deferredEvents.clear(),this.cancelAllDeferredTimers()}cancelDeferredTimer(e){const t=this.deferredTimers.get(e);t&&(clearTimeout(t),this.deferredTimers.delete(e))}cancelAllDeferredTimers(){for(const e of this.deferredTimers.values())clearTimeout(e);this.deferredTimers.clear()}scheduleDeferredTimeout(e){if(this.deferredTimers.has(e))return;const t=3e4,i=setTimeout(()=>{this.deferredTimers.delete(e);const s=this.deferredEvents.get(e);if(!(!s||s.length===0)&&!this.acpClient?.isAlive){r.error("acp-adapter",`Deferred events for session ${e} timed out after ${t/1e3}s (no ACP client)`),this.deferredEvents.delete(e);for(const{event:n}of s)this.sendTerminalEventWithoutRun(n.event_id,"failed","agent initialization timed out")}},t);this.deferredTimers.set(e,i)}startRun(e,t){if(!this.acpClient?.isAlive){this.deferEvent(e);return}const i=`acp_${++this.clientMsgSeq}_${Date.now()}`;this.activeRun={eventId:e.event_id,sessionId:e.session_id,threadId:e.thread_id,clientMsgIdBase:i,currentClientMsgId:i,currentSegmentIndex:0,chunkSeq:0,buffer:"",quotedStream:new S,markdownSegmenter:new he,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=pe(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 a=me(n.prompt,{messageId:e.msg_id,quotedMessageId:e.quoted_message_id}),o=this.injectQuoteProtocolOnce(s.sessionId,a),l=this.identity.injectOnce(s.sessionId,o);if(this.bindingStore&&s.sessionId){const u=this.callbacks.getAgentProfile?.();this.bindingStore.setIdentitySnapshot(s.sessionId,u?.agentName?.trim()??"",u?.introduction?.trim()??"")}const d=this.injectSessionContextOnce(s.sessionId,l),h=this.injectRecoveryContext(s.sessionId,d);this.sendPromptWithRetry(s,h)}injectQuoteProtocolOnce(e,t){return!e||this.quoteProtocolTaughtSessions.has(e)?t:(this.quoteProtocolTaughtSessions.add(e),_(t,xe))}injectSessionContextOnce(e,t){return!e||!this.sessionContextPending?t:(this.sessionContextPending=!1,r.info("acp-adapter",`Injected Grix session context for session=${e}`),_(t,te(e,this.callbacks.getAgentId?.())))}injectRecoveryContext(e,t){const i=this.recoveryContextBySessionId.get(e);return i?(this.recoveryContextBySessionId.delete(e),r.info("acp-adapter",`Injecting recovery context for session ${e} (chars=${i.length})`),_(t,`[recovery-context]
4
4
  ${i}
5
- [/recovery-context]`)):t}sendPromptWithRetry(e,t){this.acpClient.send(t).then(()=>{this.activeRun===e&&(e.busyWaiting=!1)}).catch(i=>{if(Ne(i)){this.scheduleAgentBusyRetry(e,t,A(i));return}if(o.error("acp-adapter",`Prompt failed: ${A(i)}`),qe(i)){const n=A(i),r=e.eventId,a=e.sessionId;o.info("acp-adapter",`Internal error escalated to bridge: event=${r} session=${a} err=${n}`),this.silentlyDiscardActiveRun(e),this.emit("internalError",{eventId:r,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>=H){o.error("acp-adapter",`agent busy wait exceeded ${Math.round(H/6e4)}min, giving up event=${e.eventId}: ${i}`),e.buffer+=`
5
+ [/recovery-context]`)):t}sendPromptWithRetry(e,t){this.acpClient.send(t).then(()=>{this.activeRun===e&&(e.busyWaiting=!1)}).catch(i=>{if(Ne(i)){this.scheduleAgentBusyRetry(e,t,A(i));return}if(r.error("acp-adapter",`Prompt failed: ${A(i)}`),qe(i)){const n=A(i),a=e.eventId,o=e.sessionId;r.info("acp-adapter",`Internal error escalated to bridge: event=${a} session=${o} err=${n}`),this.silentlyDiscardActiveRun(e),this.emit("internalError",{eventId:a,sessionId:o,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>=H){r.error("acp-adapter",`agent busy wait exceeded ${Math.round(H/6e4)}min, giving up event=${e.eventId}: ${i}`),e.buffer+=`
6
6
 
7
- 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 r=Math.min(Pe*2**e.busyRetryCount,Ee);e.busyRetryCount+=1,o.info("acp-adapter",`agent busy, keeping event queued event=${e.eventId} waited=${Math.round(n/1e3)}s retry_in=${r}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",this.describeUnreachable());return}this.resetIdleTimer(e),this.sendPromptWithRetry(e,t)}},r),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"),C(this.config.command)==="qwenpaw"&&this.config.env?.OPENAI_BASE_URL&&!e.includes("--runtime-provider")&&e.push("--runtime-provider","openai-env"),C(this.config.command)==="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 y.mkdir(p.dirname(this.reasonixAuditTranscriptPath),{recursive:!0,mode:448}),await(await y.open(this.reasonixAuditTranscriptPath,"a",384)).close(),await y.chmod(this.reasonixAuditTranscriptPath,384),e.push("--transcript",this.reasonixAuditTranscriptPath)),this.agentProcess=new V(this.bridgeLog),this.baselineGroupPids=[];const i=this.resolveCwd();try{if(!(await y.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}if(C(this.config.command)==="qwenpaw"&&!e.includes("--workspace")&&e.push("--workspace",i),await Z(this.config.command,i,this.config.env,this.nativeProviderScope).catch(s=>{o.warn("acp-adapter",`native provider config skipped: ${s instanceof Error?s.message:String(s)}`)}),C(this.config.command)==="mcode"){const s=this.config.env??{};await ee(this.config.command,{baseUrl:s.GRIX_MCODE_BASE_URL,apiKey:s.GRIX_MCODE_API_KEY,model:s.GRIX_MCODE_MODEL}).catch(n=>{o.warn("acp-adapter",`mcode provider config skipped: ${n instanceof Error?n.message:String(n)}`)})}o.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=re({...process.env,...this.config.env});o.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 o.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}o.info("acp-adapter","ACP agent process started"),this.agentProcess.on("error",s=>{this.stopped||(o.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.lastExitCode=s,!this.stopped&&(o.error("acp-adapter",`Agent process exited unexpectedly (code=${s})`),this.activeRun&&this.finishRun("failed",`agent process exited (code=${s})`),this.rejectDeferredEvents(`agent process exited (code=${s})`),this.emit("exit",s))}),this.agentProcess.on("stderr",()=>{this.activeRun&&this.resetIdleTimer(this.activeRun)})}async connectSession(e){if(this.sessionConnected||!this.agentProcess?.transport)return;let t;this.acpMcpTools&&(t=await this.startInternalApiAndMcp(e||this.resolveCwd())),this.acpClient=new ae,this.acpClient.on("event",l=>this.handleAcpEvent(l)),this.acpClient.on("activity",()=>{this.activeRun&&this.resetIdleTimer(this.activeRun)}),this.acpClient.on("session-lost",()=>{this.stopped||this.agentProcess?.alive&&(o.error("acp-adapter","ACP transport closed while agent process still alive, triggering respawn"),this.activeRun&&(this.callbacks.sendRunError(this.activeRun.eventId,this.activeRun.sessionId,"ACP transport closed"),this.finishRun("failed","ACP transport closed")),this.emit("exit",null))});const i=this.currentAibotSessionId&&this.bindingStore?this.bindingStore.getAcpSessionId(this.currentAibotSessionId):void 0,s=!!i&&i!=="__continue__",n=async l=>{const d=this.agentProcess?.transport;if(!d)throw new Error("agent process exited before the ACP session could connect");await this.acpClient.connect({transport:d,authMethod:this.acpAuthMethod,initialMode:this.acpInitialMode,initialModel:this.acpInitialModel,terminal:!0,cwd:e||this.resolveCwd(),mcpServers:t,sessionId:l,newSessionParams:this.newSessionParams})};let r=!1,a;try{await n(i)}catch(l){if(l instanceof le){await this.handleAuthRequired(l);return}if(!i)throw this.handleConnectFailure(l),l;if(o.warn("acp-adapter",`Failed to load persisted session ${i}, fallback to session/new: ${A(l)}`),r=!0,a=i,this.config.command==="kiro-cli"&&this.currentAibotSessionId){o.info("acp-adapter",`Building kiro recovery summary: aibotSession=${this.currentAibotSessionId} acpSession=${i}`);const d=await this.buildKiroRecoveryContext(this.currentAibotSessionId,i,e||this.resolveCwd());d?(this.recoveryContextBySessionId.set(this.currentAibotSessionId,d),o.info("acp-adapter",`Recovery context prepared for session ${this.currentAibotSessionId} (chars=${d.length})`)):o.warn("acp-adapter",`Recovery context skipped: no summary generated for acpSession=${i}`)}await n(void 0)}if(this.sessionConnected=!0,this.sessionContextPending=!s||r,!this.sessionContextPending){const l=this.callbacks.getAgentProfile?.(),d=l?.agentName?.trim()??"",h=l?.introduction?.trim()??"",u=I=>{const v=this.bindingStore?.getIdentitySnapshot(I);v&&v.name===d&&v.intro===h&&this.identity.markInjected(I)};this.currentAibotSessionId&&u(this.currentAibotSessionId);for(const I of this.sessionBindings.keys())u(I)}this.captureBaselineGroupPids(),o.info("acp-adapter",`ACP session ready: ${this.acpClient.sessionId}`),this.emit("acpSessionReady",this.acpClient.sessionId),this.currentAibotSessionId&&this.bindingStore&&this.bindingStore.setAcpSessionId(this.currentAibotSessionId,this.acpClient.sessionId);for(const[l,d]of this.sessionBindings){this.bindingStore&&this.acpClient.sessionId&&this.bindingStore.setAcpSessionId(l,this.acpClient.sessionId);const h=r?"binding_recreated":"binding_ready",u=this.buildToolbarContext(h,d);r&&a&&(u.previous_session_id=a,u.recreated_session_id=this.acpClient.sessionId),this.callbacks.sendUpdateBindingCard(l,"ready",d,u)}}async buildKiroRecoveryContext(e,t,i){try{const s=p.join(g(),".kiro","sessions","cli",`${t}.json`);await y.access(s),o.info("acp-adapter",`Reading kiro source session file: ${s}`);const n=await this.generateSummaryByKiroCli(s,i);if(!n)return;const r=p.join(g(),".grix","data","session-summaries");await y.mkdir(r,{recursive:!0});const a=p.join(r,`${e}.md`),l=[`SOURCE_KIRO_SESSION_FILE: ${s}`,"",n.trim(),""].join(`
8
- `);return await y.writeFile(a,l,"utf8"),o.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(`
9
- `)}catch(s){o.warn("acp-adapter",`Failed to build kiro recovery context: ${A(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(`
10
- `)],n=await this.runCommandCapture("kiro-cli",s,t,45e3);if(!n)return;const r=De(n).trim(),a=r.indexOf("> "),l=a>=0?r.slice(a+2).trim():r;if(l)return o.info("acp-adapter",`Kiro summary generated from ${e} (raw_chars=${r.length}, body_chars=${l.length})`),l.split(`
7
+ 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 a=Math.min(Pe*2**e.busyRetryCount,Ee);e.busyRetryCount+=1,r.info("acp-adapter",`agent busy, keeping event queued event=${e.eventId} waited=${Math.round(n/1e3)}s retry_in=${a}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",this.describeUnreachable());return}this.resetIdleTimer(e),this.sendPromptWithRetry(e,t)}},a),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"),C(this.config.command)==="qwenpaw"&&this.config.env?.OPENAI_BASE_URL&&!e.includes("--runtime-provider")&&e.push("--runtime-provider","openai-env"),C(this.config.command)==="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 y.mkdir(p.dirname(this.reasonixAuditTranscriptPath),{recursive:!0,mode:448}),await(await y.open(this.reasonixAuditTranscriptPath,"a",384)).close(),await y.chmod(this.reasonixAuditTranscriptPath,384),e.push("--transcript",this.reasonixAuditTranscriptPath)),this.agentProcess=new V(this.bridgeLog),this.baselineGroupPids=[];const i=this.resolveCwd();try{if(!(await y.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}if(C(this.config.command)==="qwenpaw"&&!e.includes("--workspace")&&e.push("--workspace",i),await Z(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)}`)}),C(this.config.command)==="mcode"){const s=this.config.env??{};await ee(this.config.command,{baseUrl:s.GRIX_MCODE_BASE_URL,apiKey:s.GRIX_MCODE_API_KEY,model:s.GRIX_MCODE_MODEL}).catch(n=>{r.warn("acp-adapter",`mcode provider config skipped: ${n instanceof Error?n.message:String(n)}`)})}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=re({...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.lastExitCode=s,!this.stopped&&(r.error("acp-adapter",`Agent process exited unexpectedly (code=${s})`),this.activeRun&&this.finishRun("failed",`agent process exited (code=${s})`),this.rejectDeferredEvents(`agent process exited (code=${s})`),this.emit("exit",s))}),this.agentProcess.on("stderr",()=>{this.activeRun&&this.resetIdleTimer(this.activeRun)})}async connectSession(e){if(this.sessionConnected||!this.agentProcess?.transport)return;let t;this.acpMcpTools&&(t=await this.startInternalApiAndMcp(e||this.resolveCwd())),this.acpClient=new ae,this.acpClient.on("event",l=>this.handleAcpEvent(l)),this.acpClient.on("activity",()=>{this.activeRun&&this.resetIdleTimer(this.activeRun)}),this.acpClient.on("session-lost",()=>{this.stopped||this.agentProcess?.alive&&(r.error("acp-adapter","ACP transport closed while agent process still alive, triggering respawn"),this.activeRun&&(this.callbacks.sendRunError(this.activeRun.eventId,this.activeRun.sessionId,"ACP transport closed"),this.finishRun("failed","ACP transport closed")),this.emit("exit",null))});const i=this.currentAibotSessionId&&this.bindingStore?this.bindingStore.getAcpSessionId(this.currentAibotSessionId):void 0,s=!!i&&i!=="__continue__",n=async l=>{const d=this.agentProcess?.transport;if(!d)throw new Error("agent process exited before the ACP session could connect");await this.acpClient.connect({transport:d,authMethod:this.acpAuthMethod,initialMode:this.acpInitialMode,initialModel:this.acpInitialModel,terminal:!0,cwd:e||this.resolveCwd(),mcpServers:t,sessionId:l,newSessionParams:this.newSessionParams})};let a=!1,o;try{await n(i)}catch(l){if(l instanceof le){await this.handleAuthRequired(l);return}if(!i)throw this.handleConnectFailure(l),l;if(r.warn("acp-adapter",`Failed to load persisted session ${i}, fallback to session/new: ${A(l)}`),a=!0,o=i,this.config.command==="kiro-cli"&&this.currentAibotSessionId){r.info("acp-adapter",`Building kiro recovery summary: aibotSession=${this.currentAibotSessionId} acpSession=${i}`);const d=await this.buildKiroRecoveryContext(this.currentAibotSessionId,i,e||this.resolveCwd());d?(this.recoveryContextBySessionId.set(this.currentAibotSessionId,d),r.info("acp-adapter",`Recovery context prepared for session ${this.currentAibotSessionId} (chars=${d.length})`)):r.warn("acp-adapter",`Recovery context skipped: no summary generated for acpSession=${i}`)}await n(void 0)}if(this.sessionConnected=!0,this.sessionContextPending=!s||a,!this.sessionContextPending){const l=this.callbacks.getAgentProfile?.(),d=l?.agentName?.trim()??"",h=l?.introduction?.trim()??"",u=I=>{const v=this.bindingStore?.getIdentitySnapshot(I);v&&v.name===d&&v.intro===h&&this.identity.markInjected(I)};this.currentAibotSessionId&&u(this.currentAibotSessionId);for(const I of this.sessionBindings.keys())u(I)}this.captureBaselineGroupPids(),r.info("acp-adapter",`ACP session ready: ${this.acpClient.sessionId}`),this.emit("acpSessionReady",this.acpClient.sessionId),this.currentAibotSessionId&&this.bindingStore&&this.bindingStore.setAcpSessionId(this.currentAibotSessionId,this.acpClient.sessionId);for(const[l,d]of this.sessionBindings){this.bindingStore&&this.acpClient.sessionId&&this.bindingStore.setAcpSessionId(l,this.acpClient.sessionId);const h=a?"binding_recreated":"binding_ready",u=this.buildToolbarContext(h,d);a&&o&&(u.previous_session_id=o,u.recreated_session_id=this.acpClient.sessionId),this.callbacks.sendUpdateBindingCard(l,"ready",d,u)}}async buildKiroRecoveryContext(e,t,i){try{const s=p.join(g(),".kiro","sessions","cli",`${t}.json`);await y.access(s),r.info("acp-adapter",`Reading kiro source session file: ${s}`);const n=await this.generateSummaryByKiroCli(s,i);if(!n)return;const a=p.join(g(),".grix","data","session-summaries");await y.mkdir(a,{recursive:!0});const o=p.join(a,`${e}.md`),l=[`SOURCE_KIRO_SESSION_FILE: ${s}`,"",n.trim(),""].join(`
8
+ `);return await y.writeFile(o,l,"utf8"),r.info("acp-adapter",`Recovery summary saved: ${o} (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: ${o}`,"",n.trim()].join(`
9
+ `)}catch(s){r.warn("acp-adapter",`Failed to build kiro recovery context: ${A(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(`
10
+ `)],n=await this.runCommandCapture("kiro-cli",s,t,45e3);if(!n)return;const a=De(n).trim(),o=a.indexOf("> "),l=o>=0?a.slice(o+2).trim():a;if(l)return r.info("acp-adapter",`Kiro summary generated from ${e} (raw_chars=${a.length}, body_chars=${l.length})`),l.split(`
11
11
  `).filter(d=>!d.includes("Credits:")&&!d.includes("Time:")).join(`
12
- `).trim()}runCommandCapture(e,t,i,s){return new Promise((n,r)=>{const a=M(e,t,{cwd:i,stdio:["ignore","pipe","pipe"],windowsHide:!0});let l="",d="";const h=setTimeout(()=>{a.kill("SIGTERM"),r(new Error(`${e} timed out after ${s}ms`))},s);a.stdout.on("data",u=>{l+=String(u)}),a.stderr.on("data",u=>{d+=String(u)}),a.on("error",u=>{clearTimeout(h),r(u)}),a.on("close",u=>{if(clearTimeout(h),u===0){n(l||d);return}r(new Error(`${e} exited with code ${u}: ${d||l}`))})})}handleConnectFailure(e){o.error("acp-adapter",`ACP session creation failed: ${A(e)}`),this.acpClient?.dispose(),this.acpClient?.removeAllListeners(),this.acpClient=null,this.emit("exit",null)}async startInternalApiAndMcp(e=this.resolveCwd()){try{this.internalApi||(this.internalApi=new de,this.internalApi.setInvokeHandler(async(a,l,d,h)=>this.callbacks.agentInvoke(a,l,h)),await this.internalApi.start(0),o.info("acp-adapter",`Internal API started at ${this.internalApi.url}`)),this.internalApi.setMcpBridgeUpHandler(a=>this.onMcpBridgeUp(a));const t=this.config.command??"",i=C(t)==="qwen"?p.join(g(),".qwen","skills"):t.includes("gemini")?p.join(g(),".gemini","skills"):t.includes("kiro")?p.join(g(),".kiro","skills"):this.isReasonix?p.join(g(),".reasonix","skills"):this.isKimi?p.join(ne({...process.env,...this.config.env}),"skills"):this.auditAgentType==="copilot"||/^copilot(?:\.cmd)?$/i.test(p.basename(t).toLowerCase())||/^gh(?:\.exe)?$/i.test(p.basename(t).toLowerCase())&&(this.config.args??[]).includes("copilot")?p.join(g(),".copilot","skills"):this.auditAgentType==="hermes"?p.join(this.config.env?.HERMES_HOME?.trim()||oe({...process.env,...this.config.env},this.nativeProviderScope),"skills"):this.auditAgentType==="qodercli"?p.join(g(),".qoder","skills"):this.auditAgentType==="qoderclicn"?p.join(g(),".qoder-cn","skills"):this.auditAgentType==="traecli"?p.join(g(),".trae","skills"):this.auditAgentType==="codebuddy"?p.join(g(),".codebuddy","skills"):void 0;if(i){const a=se(i);a.length>0&&o.info("acp-adapter",`Synced connector skills to ${i}: [${a.join(", ")}]`)}const s=p.resolve(R,"../../mcp/mcp-bridge-server.js"),n=this.internalApi.mcpBridgeWsUrl,r=p.resolve(R,"../../mcp/acp-mcp-server.js");if(this.isKimi){await this.internalApi.setCodebaseNavigatorCwd(e);const a={Authorization:`Bearer ${this.internalApi.token}`};return[{type:"http",name:"grix-app-bridge",url:this.internalApi.mcpBridgeHttpUrl,headers:a},{type:"http",name:"grix-connector-tools",url:this.internalApi.mcpToolsHttpUrl,headers:a},{type:"http",name:"codebase-navigator",url:this.internalApi.codebaseNavigatorHttpUrl,headers:a}]}return[{name:"grix-app-bridge",command:process.execPath,args:[s,"--ws-url",n],...!this.isReasonix&&{env:{GRIX_CONNECTOR_MCP_BRIDGE_WS:n}}},{name:"grix-connector-tools",command:process.execPath,args:[r,"--api-url",this.internalApi.url],...!this.isReasonix&&{env:{}}}]}catch(t){o.error("acp-adapter",`Failed to start MCP tools: ${t}`);return}}onMcpBridgeUp(e){this.callbacks.sendMcpFrame?.(e)}deliverMcpFrameToAgent(e){this.internalApi?.sendMcpFrameToBridge(e)}cleanup(){this.flushOrphanBuffer(),this.activeRun,this.pendingApprovals.clear(),this.pendingQuestions.clear(),this.pendingAutoCompact=!1,this.rejectDeferredEvents("adapter cleaned up"),this.cancelAllDeferredTimers(),this.sessionConnected=!1,this.acpClient&&(this.acpClient.dispose(),this.acpClient.removeAllListeners(),this.acpClient=null),this.agentProcess&&(this.agentProcess.removeAllListeners(),this.agentProcess=null),this.baselineGroupPids=[],this.terminalAuthChild&&(this.terminalAuthChild.kill("SIGKILL"),this.terminalAuthChild=null)}async handleAuthRequired(e){o.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:
12
+ `).trim()}runCommandCapture(e,t,i,s){return new Promise((n,a)=>{const o=M(e,t,{cwd:i,stdio:["ignore","pipe","pipe"],windowsHide:!0});let l="",d="";const h=setTimeout(()=>{o.kill("SIGTERM"),a(new Error(`${e} timed out after ${s}ms`))},s);o.stdout.on("data",u=>{l+=String(u)}),o.stderr.on("data",u=>{d+=String(u)}),o.on("error",u=>{clearTimeout(h),a(u)}),o.on("close",u=>{if(clearTimeout(h),u===0){n(l||d);return}a(new Error(`${e} exited with code ${u}: ${d||l}`))})})}handleConnectFailure(e){r.error("acp-adapter",`ACP session creation failed: ${A(e)}`),this.acpClient?.dispose(),this.acpClient?.removeAllListeners(),this.acpClient=null,this.emit("exit",null)}async startInternalApiAndMcp(e=this.resolveCwd()){try{this.internalApi||(this.internalApi=new de,this.internalApi.setInvokeHandler(async(o,l,d,h)=>{if(o==="toolbar_set"&&!String(l?.session_id??"").trim()){const u=String(this.currentAibotSessionId??"").trim();u&&(l={...l,session_id:u})}return this.callbacks.agentInvoke(o,l,h)}),await this.internalApi.start(0),r.info("acp-adapter",`Internal API started at ${this.internalApi.url}`)),this.internalApi.setMcpBridgeUpHandler(o=>this.onMcpBridgeUp(o));const t=this.config.command??"",i=C(t)==="qwen"?p.join(g(),".qwen","skills"):t.includes("gemini")?p.join(g(),".gemini","skills"):t.includes("kiro")?p.join(g(),".kiro","skills"):this.isReasonix?p.join(g(),".reasonix","skills"):this.isKimi?p.join(ne({...process.env,...this.config.env}),"skills"):this.auditAgentType==="copilot"||/^copilot(?:\.cmd)?$/i.test(p.basename(t).toLowerCase())||/^gh(?:\.exe)?$/i.test(p.basename(t).toLowerCase())&&(this.config.args??[]).includes("copilot")?p.join(g(),".copilot","skills"):this.auditAgentType==="hermes"?p.join(this.config.env?.HERMES_HOME?.trim()||oe({...process.env,...this.config.env},this.nativeProviderScope),"skills"):this.auditAgentType==="qodercli"?p.join(g(),".qoder","skills"):this.auditAgentType==="qoderclicn"?p.join(g(),".qoder-cn","skills"):this.auditAgentType==="traecli"?p.join(g(),".trae","skills"):this.auditAgentType==="codebuddy"?p.join(g(),".codebuddy","skills"):void 0;if(i){const o=se(i);o.length>0&&r.info("acp-adapter",`Synced connector skills to ${i}: [${o.join(", ")}]`)}const s=p.resolve(R,"../../mcp/mcp-bridge-server.js"),n=this.internalApi.mcpBridgeWsUrl,a=p.resolve(R,"../../mcp/acp-mcp-server.js");if(this.isKimi){await this.internalApi.setCodebaseNavigatorCwd(e);const o={Authorization:`Bearer ${this.internalApi.token}`};return[{type:"http",name:"grix-app-bridge",url:this.internalApi.mcpBridgeHttpUrl,headers:o},{type:"http",name:"grix-connector-tools",url:this.internalApi.mcpToolsHttpUrl,headers:o},{type:"http",name:"codebase-navigator",url:this.internalApi.codebaseNavigatorHttpUrl,headers:o}]}return[{name:"grix-app-bridge",command:process.execPath,args:[s,"--ws-url",n],...!this.isReasonix&&{env:{GRIX_CONNECTOR_MCP_BRIDGE_WS:n}}},{name:"grix-connector-tools",command:process.execPath,args:[a,"--api-url",this.internalApi.url],...!this.isReasonix&&{env:{}}}]}catch(t){r.error("acp-adapter",`Failed to start MCP tools: ${t}`);return}}onMcpBridgeUp(e){this.callbacks.sendMcpFrame?.(e)}deliverMcpFrameToAgent(e){this.internalApi?.sendMcpFrameToBridge(e)}cleanup(){this.flushOrphanBuffer(),this.activeRun,this.pendingApprovals.clear(),this.pendingQuestions.clear(),this.pendingAutoCompact=!1,this.rejectDeferredEvents("adapter cleaned up"),this.cancelAllDeferredTimers(),this.sessionConnected=!1,this.acpClient&&(this.acpClient.dispose(),this.acpClient.removeAllListeners(),this.acpClient=null),this.agentProcess&&(this.agentProcess.removeAllListeners(),this.agentProcess=null),this.baselineGroupPids=[],this.terminalAuthChild&&(this.terminalAuthChild.kill("SIGKILL"),this.terminalAuthChild=null)}async handleAuthRequired(e){r.info("acp-adapter",`Auth required, methods: ${e.authMethods.map(s=>s.id).join(", ")}`);const t=e.authMethods.find(s=>/oauth|browser/i.test(s.id))??e.authMethods[0];if(!t)throw e;const i=this.currentAibotSessionId??"";this.callbacks.sendAuthNotification(i,`Authentication required (${t.id}). Initiating auth flow...`);for(const[s,n]of this.sessionBindings)this.callbacks.sendUpdateBindingCard(s,"failed",n);if(t.type==="terminal")await this.runTerminalAuth(t,i);else{const s=await this.captureAuthUrl();s&&this.callbacks.sendAuthNotification(i,`Please open this URL to authenticate:
13
13
  ${s}
14
14
 
15
- Waiting for authentication to complete...`)}try{await this.acpClient.authenticate(t.id),o.info("acp-adapter","Authentication successful"),this.callbacks.sendAuthNotification(i,"Authentication successful. Resuming...");const s=this.acpMcpTools?await this.startInternalApiAndMcp():void 0,n=this.agentProcess?.transport;if(!n||!this.acpClient)throw new Error("agent process exited during authentication");await this.acpClient.connect({transport:n,initialMode:this.acpInitialMode,initialModel:this.acpInitialModel,terminal:!0,cwd:this.resolveCwd(),mcpServers:s,newSessionParams:this.newSessionParams}),o.info("acp-adapter",`ACP session ready after auth: ${this.acpClient.sessionId}`),this.captureBaselineGroupPids(),this.emit("acpSessionReady",this.acpClient.sessionId);for(const[r,a]of this.sessionBindings)this.callbacks.sendUpdateBindingCard(r,"ready",a,this.buildToolbarContext("binding_ready",a))}catch(s){throw o.error("acp-adapter",`Auth retry failed: ${s}`),s}}runTerminalAuth(e,t){return new Promise((i,s)=>{const n=M(this.config.command,e.args??[],{env:{...process.env,...this.config.env??{}},stdio:["ignore","pipe","pipe"],windowsHide:!0});this.terminalAuthChild=n;const r=/https?:\/\/[^\s"')\]]+/;let a=!1,l=!1;const d=u=>{if(a)return;const v=u.toString().replace(/\x1b\[[0-9;]*m/g,"").match(r);v&&(a=!0,o.info("acp-adapter",`Terminal auth URL captured: ${v[0]}`),this.callbacks.sendAuthNotification(t,`Please open this URL on any device to authenticate:
15
+ 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,n=this.agentProcess?.transport;if(!n||!this.acpClient)throw new Error("agent process exited during authentication");await this.acpClient.connect({transport:n,initialMode:this.acpInitialMode,initialModel:this.acpInitialModel,terminal:!0,cwd:this.resolveCwd(),mcpServers:s,newSessionParams:this.newSessionParams}),r.info("acp-adapter",`ACP session ready after auth: ${this.acpClient.sessionId}`),this.captureBaselineGroupPids(),this.emit("acpSessionReady",this.acpClient.sessionId);for(const[a,o]of this.sessionBindings)this.callbacks.sendUpdateBindingCard(a,"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=M(this.config.command,e.args??[],{env:{...process.env,...this.config.env??{}},stdio:["ignore","pipe","pipe"],windowsHide:!0});this.terminalAuthChild=n;const a=/https?:\/\/[^\s"')\]]+/;let o=!1,l=!1;const d=u=>{if(o)return;const v=u.toString().replace(/\x1b\[[0-9;]*m/g,"").match(a);v&&(o=!0,r.info("acp-adapter",`Terminal auth URL captured: ${v[0]}`),this.callbacks.sendAuthNotification(t,`Please open this URL on any device to authenticate:
16
16
  ${v[0]}
17
17
 
18
- Waiting for authentication to complete...`))};n.stdout?.on("data",d),n.stderr?.on("data",d);const h=setTimeout(()=>{l||(l=!0,n.kill("SIGKILL"),this.terminalAuthChild=null,s(new Error(`terminal auth timed out after ${W}ms`)))},W);n.on("error",u=>{l||(l=!0,clearTimeout(h),this.terminalAuthChild=null,s(u))}),n.on("exit",u=>{l||(l=!0,clearTimeout(h),this.terminalAuthChild=null,u===0?i():s(new Error(`terminal auth process exited with code ${u}`)))})})}captureAuthUrl(){return new Promise(e=>{if(!this.agentProcess){e(null);return}const t=/https?:\/\/[^\s"')\]]+/;let i=!1;const s=n=>{if(i)return;const a=n.toString().replace(/\x1b\[[0-9;]*m/g,"").match(t);a&&(i=!0,this.agentProcess.removeListener("stderr",s),e(a[0]))};this.agentProcess.on("stderr",s),setTimeout(()=>{i||(i=!0,this.agentProcess.removeListener("stderr",s),e(null))},3e4)})}handleAcpEvent(e){try{o.info("acp-adapter","incoming acp event",{eventType:e.type,sessionId:this.activeRun?.sessionId??null,eventKeys:Object.keys(e),rawEvent:q(e,2e3)})}catch{}if(e.type===f.PermissionRequest){this.activeRun&&this.resetIdleTimer(this.activeRun),this.captureAuditEvent(e),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=T(s);if(t.lastToolUse?.toolName===e.toolName&&T(t.lastToolUse.toolInput)===n)break;t.lastToolUse={toolName:e.toolName,toolInput:s},e.toolCallId&&t.sentToolUseIds.add(e.toolCallId);const r=this.trimToolWirePayload?x(s):s;this.emitRawEventEnvelope(t,{type:"tool_use",payload:{tool_name:e.toolName,tool_input:r}})||this.callbacks.sendToolUse(t.eventId,t.sessionId,e.toolName,r)}break}case f.ToolResult:{t.awaitingToolResult=!1;{const s=e.toolCallId||e.toolName||"";t.lastToolProgress.delete(s),t.lastToolProgressSentAt.delete(s)}if(t.lastToolUse=null,e.content){i=!0;const s=N(t,e),n=e.content,r=T(n);if(t.lastToolResult?.toolName===s&&T(t.lastToolResult.content)===r)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(),r=t.lastToolProgressSentAt.get(s);if(r!==void 0&&n-r<fe)break;t.lastToolProgressSentAt.set(s,n);const a=N(t,e);this.emitRawEventEnvelope(t,{type:"tool_progress",payload:{tool_name:a,content:e.content}})||this.callbacks.sendToolResult(t.eventId,t.sessionId,a,e.content)}break}case f.Thinking:{t.lastToolUse=null,t.lastToolResult=null,e.content&&(i=!0,this.emitRawEventEnvelope(t,{type:"thinking",payload:{content:e.content}})||this.callbacks.sendThinking(t.eventId,t.sessionId,e.content));break}case f.Error:{t.lastToolUse=null,t.lastToolResult=null;const s=String(e.error??"unknown error");this.emitRawEventEnvelope(t,{type:"error",payload:{message:s}}),o.error("acp-adapter",`ACP error: ${s}`),this.callbacks.sendRunError(t.eventId,t.sessionId,s);break}case f.Result:{if(e.synthetic&&this.shouldDeferSyntheticResultForDetachedKimiTask(t,e)){o.info("acp-adapter",`Synthetic ACP Result deferred: kimi detached task still running event=${t.eventId} session=${t.sessionId}`),this.acpClient?.deferSettledResult();return}this.emitRawEventEnvelope(t,{type:"result",payload:{done:e.done??!0}});const s=t.quotedStream.flush();s.deltaContent&&this.appendToStream(t,s.deltaContent),s.quotedMessageId&&(t.quotedMessageId=s.quotedMessageId),this.flushStream(),this.finishRun("responded");break}}i&&(t.lastProgressAt=Date.now(),t.idleNoProgressCount=0),this.resetIdleTimer(t)}handlePermissionRequest(e){const t=e.permissionRequest;if(o.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:q(t,3e3)}),!t||!e.requestId||!this.acpClient){o.warn("acp-adapter","permission_request dropped: missing prerequisite",{hasPr:t!=null,hasRequestId:!!e.requestId,hasAcpClient:!!this.acpClient}),e.requestId&&this.captureAuditEvent({type:f.PermissionResponse,requestId:e.requestId,permissionDecision:"dropped"});return}if(this.approvalMode==="yolo"||this.approvalMode==="autoEdit"){o.info("acp-adapter",`Auto-approving (${this.approvalMode}): ${t.toolName}`),this.acpClient.respondPermission(t.requestId,{behavior:"allow"}).catch(()=>{}),this.captureAuditEvent({type:f.PermissionResponse,requestId:t.requestId,toolCallId:t.toolCallId,permissionDecision:"allow"});return}const i=this.activeRun,s=ye(t);if(s&&i&&this.callbacks.sendAgentQuestionCard){this.resetIdleTimer(i),this.pendingQuestions.set(e.requestId,{options:[...t.options]}),this.callbacks.sendAgentQuestionCard(i.eventId,i.sessionId,{request_id:e.requestId,mode:"form",questions:[{header:s.prompt,prompt:s.prompt,options:s.answerOptions.map(a=>a.name)}]}),o.info("acp-adapter","AskUserQuestion forwarded via agent_question card",{sessionId:i.sessionId,eventId:i.eventId,requestId:e.requestId,optionsCount:s.answerOptions.length});return}const n=t.toolCallId,r=ge(t.rawParams,e.toolInput,t.toolTitle);this.pendingApprovals.set(n,e.requestId),i?(i.idleTimer&&(clearTimeout(i.idleTimer),i.idleTimer=null),this.emitRawEventEnvelope(i,{type:"permission_request",payload:{request_id:t.requestId,tool_call_id:n,tool_name:t.toolName,tool_title:t.toolTitle,...r?{tool_input:r}:{},options:t.options}})?o.info("acp-adapter","permission_request forwarded via rawEventEnvelope",{sessionId:i.sessionId,eventId:i.eventId,toolCallId:n,toolName:t.toolName}):(o.info("acp-adapter","permission_request falling back to sendPermissionCard",{sessionId:i.sessionId,eventId:i.eventId,toolCallId:n,toolName:t.toolName}),this.callbacks.sendPermissionCard({eventId:i.eventId,sessionId:i.sessionId,toolCallId:n,toolName:t.toolName,toolTitle:t.toolTitle,toolInput:r,options:t.options}))):(o.info("acp-adapter",`Permission request without active run, auto-approving: ${t.toolName}`),this.acpClient.respondPermission(e.requestId,{behavior:"allow"}),this.pendingApprovals.delete(n),this.captureAuditEvent({type:f.PermissionResponse,requestId:e.requestId,toolCallId:n,permissionDecision:"allow"}))}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?ke:Re),s=e.lastProgressAt+b-Date.now(),n=Math.max(0,Math.min(i,s));e.idleTimer=setTimeout(()=>{this.onIdleTimeout(e)},n)}async onIdleTimeout(e){if(this.activeRun?.eventId!==e.eventId)return;const t=this.acpClient,i=Date.now()-e.lastProgressAt;if(i>=b){if(t?.isAlive){const s=e.lastProgressAt,n=await this.checkSessionFileActivity(t.sessionId,e.lastIdleCheckAt);if(this.activeRun?.eventId!==e.eventId)return;const r=e.lastProgressAt>s;if(e.lastIdleCheckAt=Date.now(),r||n){n&&(e.lastProgressAt=Date.now()),e.idleNoProgressCount=0,o.info("acp-adapter",`Hard cap recheck found ${r?"stream progress":"session file activity"}, refreshing watchdog: ${e.eventId}`),this.resetIdleTimer(e);return}}o.error("acp-adapter",`Turn inactive ${Math.round(i/1e3)}s \u2265 cap ${Math.round(b/1e3)}s, forcing timeout: ${e.eventId}`),this.finishRun("failed",`turn inactivity timeout after ${Math.round(i/1e3)}s with no ACP activity`),this.declareStuck();return}t?.isAlive?t.ping(5e3).then(async s=>{if(this.activeRun?.eventId!==e.eventId)return;if(!s){await this.handlePingFailure(e,t);return}if(e.consecutivePingFailures=0,e.awaitingToolResult){const d=e.lastIdleCheckAt,h=await this.checkSessionFileActivity(t.sessionId,d);if(this.activeRun?.eventId!==e.eventId)return;e.lastIdleCheckAt=Date.now(),h?(e.lastProgressAt=Date.now(),o.info("acp-adapter",`Idle timer: tool running, ping ok, session file active, refreshing watchdog: ${e.eventId}`)):o.info("acp-adapter",`Idle timer: tool running, ping ok, continuing until inactivity cap: ${e.eventId}`),e.idleNoProgressCount=0,this.resetIdleTimer(e);return}const n=e.lastIdleCheckAt,r=e.lastProgressAt>n,a=r?!1:await this.checkSessionFileActivity(t.sessionId,n);if(this.activeRun?.eventId!==e.eventId)return;const l=r||a;e.lastIdleCheckAt=Date.now(),l?(a&&(o.info("acp-adapter",`Idle timer: no stream progress but session file active, continuing: ${e.eventId}`),e.lastProgressAt=Date.now()),e.idleNoProgressCount=0,this.resetIdleTimer(e)):(e.idleNoProgressCount++,o.info("acp-adapter",`Idle timer: ping ok, no stream progress (cycle=${e.idleNoProgressCount}), extending until inactivity cap (${Math.round(b/6e4)}min): ${e.eventId}`),this.resetIdleTimer(e))}).catch(()=>{this.activeRun?.eventId===e.eventId&&this.handlePingFailure(e,t)}):(o.error("acp-adapter",`Agent idle (no client), declaring stuck: ${e.eventId}`),this.finishRun("failed",this.describeUnreachable()),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||this.pendingQuestions.size>0){const r=s?"stream progress":n?"session file activity":e.awaitingToolResult?"tool in flight":"question pending user answer";o.info("acp-adapter",`Idle timer: ping failed but ${r}, continuing: ${e.eventId}`),e.consecutivePingFailures=0,e.lastIdleCheckAt=Date.now(),n&&(e.lastProgressAt=Date.now()),this.resetIdleTimer(e);return}if(e.consecutivePingFailures++,e.lastIdleCheckAt=Date.now(),e.consecutivePingFailures<j){o.warn("acp-adapter",`Idle timer: ping failed (${e.consecutivePingFailures}/${j}), rechecking in ${L/1e3}s: ${e.eventId}`),this.resetIdleTimer(e,L);return}o.error("acp-adapter",`Ping failed ${e.consecutivePingFailures}x with no activity evidence, declaring stuck: ${e.eventId}`),this.finishRun("failed",this.describeUnreachable()),this.declareStuck()}}async checkSessionFileActivity(e,t){if(!e)return!1;try{if(this.isKimi)return await Ce(e,t);const i=C(this.config.command);return i==="kiro-cli"?await Ae(e,t):i==="qodercli"?await F(".qoder",e,t):i==="qoderclicn"?await F(".qoder-cn",e,t):i==="dim"?await Se(e,t):!1}catch{return!1}}shouldDeferSyntheticResultForDetachedKimiTask(e,t){if(!this.isKimi)return!1;try{return Te(t.sessionId||this.acpClient?.sessionId||e.sessionId)}catch{return!1}}declareStuck(){this.pendingQuestions.clear(),this.acpClient&&(this.acpClient.dispose(),this.acpClient.removeAllListeners(),this.acpClient=null),this.agentProcess&&(this.agentProcess.close().catch(()=>{}),this.agentProcess=null),this.emit("stuck")}appendToStream(e,t){e.buffer+=t,e.flushTimer||(e.flushTimer=setTimeout(()=>this.flushStream(),_e))}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,o.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>=Me?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()},$e),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 S;const t=this.orphanSessionId||this.currentAibotSessionId;this.orphanSessionId=null;const i=this.orphanBuffer.trim();if(this.orphanBuffer="",!!i){if(!t||!this.callbacks.sendAgentMessage){o.warn("acp-adapter",`orphan agent output dropped (${t?"no sendAgentMessage callback":"no session id"}) chars=${i.length}`);return}o.info("acp-adapter",`delivering orphan agent output session=${t} chars=${i.length}`);for(let s=0;s<i.length;s+=G)this.callbacks.sendAgentMessage(t,i.slice(s,s+G))}}suppressOrphanOutput(e){this.orphanSuppressedUntil=Date.now()+e,this.orphanFlushTimer&&(clearTimeout(this.orphanFlushTimer),this.orphanFlushTimer=null),this.orphanBuffer="",this.orphanQuotedStream=new S,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)}lastExitCode=null;describeUnreachable(){return this.agentProcess?.alive?"agent unreachable: no response while its process is still running":this.lastExitCode===null?"agent unreachable: its process is no longer running":`agent unreachable: its process exited (code=${this.lastExitCode})`}static sanitizeFailureReasonForUser(e){return e.replace(/\bunreachable\b/gi,"unresponsive").replace(/\bidle cycles?\b/gi,"consecutive checks")}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);let n=!1;if(t)if(e==="failed"){o.error("acp-adapter",`finishRun failed: ${t} event=${i.eventId}`);const a=z.sanitizeFailureReasonForUser(t);i.buffer+=`
18
+ Waiting for authentication to complete...`))};n.stdout?.on("data",d),n.stderr?.on("data",d);const h=setTimeout(()=>{l||(l=!0,n.kill("SIGKILL"),this.terminalAuthChild=null,s(new Error(`terminal auth timed out after ${W}ms`)))},W);n.on("error",u=>{l||(l=!0,clearTimeout(h),this.terminalAuthChild=null,s(u))}),n.on("exit",u=>{l||(l=!0,clearTimeout(h),this.terminalAuthChild=null,u===0?i():s(new Error(`terminal auth process exited with code ${u}`)))})})}captureAuthUrl(){return new Promise(e=>{if(!this.agentProcess){e(null);return}const t=/https?:\/\/[^\s"')\]]+/;let i=!1;const s=n=>{if(i)return;const o=n.toString().replace(/\x1b\[[0-9;]*m/g,"").match(t);o&&(i=!0,this.agentProcess.removeListener("stderr",s),e(o[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:q(e,2e3)})}catch{}if(e.type===f.PermissionRequest){this.activeRun&&this.resetIdleTimer(this.activeRun),this.captureAuditEvent(e),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=b(s);if(t.lastToolUse?.toolName===e.toolName&&b(t.lastToolUse.toolInput)===n)break;t.lastToolUse={toolName:e.toolName,toolInput:s},e.toolCallId&&t.sentToolUseIds.add(e.toolCallId);const a=this.trimToolWirePayload?x(s):s;this.emitRawEventEnvelope(t,{type:"tool_use",payload:{tool_name:e.toolName,tool_input:a}})||this.callbacks.sendToolUse(t.eventId,t.sessionId,e.toolName,a)}break}case f.ToolResult:{t.awaitingToolResult=!1;{const s=e.toolCallId||e.toolName||"";t.lastToolProgress.delete(s),t.lastToolProgressSentAt.delete(s)}if(t.lastToolUse=null,e.content){i=!0;const s=N(t,e),n=e.content,a=b(n);if(t.lastToolResult?.toolName===s&&b(t.lastToolResult.content)===a)break;if(t.lastToolResult={toolName:s,content:n},!this.trimToolWirePayload||e.toolStatus==="failed"){const o=this.trimToolWirePayload?"(failed)":n;this.emitRawEventEnvelope(t,{type:"tool_result",payload:{tool_name:s,content:o}})||this.callbacks.sendToolResult(t.eventId,t.sessionId,s,o)}}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(),a=t.lastToolProgressSentAt.get(s);if(a!==void 0&&n-a<fe)break;t.lastToolProgressSentAt.set(s,n);const o=N(t,e);this.emitRawEventEnvelope(t,{type:"tool_progress",payload:{tool_name:o,content:e.content}})||this.callbacks.sendToolResult(t.eventId,t.sessionId,o,e.content)}break}case f.Thinking:{t.lastToolUse=null,t.lastToolResult=null,e.content&&(i=!0,this.emitRawEventEnvelope(t,{type:"thinking",payload:{content:e.content}})||this.callbacks.sendThinking(t.eventId,t.sessionId,e.content));break}case f.Error:{t.lastToolUse=null,t.lastToolResult=null;const s=String(e.error??"unknown error");this.emitRawEventEnvelope(t,{type:"error",payload:{message:s}}),r.error("acp-adapter",`ACP error: ${s}`),this.callbacks.sendRunError(t.eventId,t.sessionId,s);break}case f.Result:{if(e.synthetic&&this.shouldDeferSyntheticResultForDetachedKimiTask(t,e)){r.info("acp-adapter",`Synthetic ACP Result deferred: kimi detached task still running event=${t.eventId} session=${t.sessionId}`),this.acpClient?.deferSettledResult();return}this.emitRawEventEnvelope(t,{type:"result",payload:{done:e.done??!0}});const s=t.quotedStream.flush();s.deltaContent&&this.appendToStream(t,s.deltaContent),s.quotedMessageId&&(t.quotedMessageId=s.quotedMessageId),this.flushStream(),this.finishRun("responded");break}}i&&(t.lastProgressAt=Date.now(),t.idleNoProgressCount=0),this.resetIdleTimer(t)}handlePermissionRequest(e){const t=e.permissionRequest;if(r.info("acp-adapter","permission_request received",{sessionId:this.activeRun?.sessionId,eventId:this.activeRun?.eventId,requestId:e.requestId??null,toolCallId:t?.toolCallId??null,toolName:t?.toolName??null,toolTitle:t?.toolTitle??null,optionsCount:Array.isArray(t?.options)?t.options.length:0,hasActiveRun:this.activeRun!==null,approvalMode:this.approvalMode,rawTransport:this.rawTransport,hasRawEventEnvelopeCb:typeof this.callbacks.sendRawEventEnvelope=="function",hasSendPermissionCardCb:typeof this.callbacks.sendPermissionCard=="function",rawPr:q(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}),e.requestId&&this.captureAuditEvent({type:f.PermissionResponse,requestId:e.requestId,permissionDecision:"dropped"});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(()=>{}),this.captureAuditEvent({type:f.PermissionResponse,requestId:t.requestId,toolCallId:t.toolCallId,permissionDecision:"allow"});return}const i=this.activeRun,s=ye(t);if(s&&i&&this.callbacks.sendAgentQuestionCard){this.resetIdleTimer(i),this.pendingQuestions.set(e.requestId,{options:[...t.options]}),this.callbacks.sendAgentQuestionCard(i.eventId,i.sessionId,{request_id:e.requestId,mode:"form",questions:[{header:s.prompt,prompt:s.prompt,options:s.answerOptions.map(o=>o.name)}]}),r.info("acp-adapter","AskUserQuestion forwarded via agent_question card",{sessionId:i.sessionId,eventId:i.eventId,requestId:e.requestId,optionsCount:s.answerOptions.length});return}const n=t.toolCallId,a=ge(t.rawParams,e.toolInput,t.toolTitle);this.pendingApprovals.set(n,e.requestId),i?(i.idleTimer&&(clearTimeout(i.idleTimer),i.idleTimer=null),this.emitRawEventEnvelope(i,{type:"permission_request",payload:{request_id:t.requestId,tool_call_id:n,tool_name:t.toolName,tool_title:t.toolTitle,...a?{tool_input:a}:{},options:t.options}})?r.info("acp-adapter","permission_request forwarded via rawEventEnvelope",{sessionId:i.sessionId,eventId:i.eventId,toolCallId:n,toolName:t.toolName}):(r.info("acp-adapter","permission_request falling back to sendPermissionCard",{sessionId:i.sessionId,eventId:i.eventId,toolCallId:n,toolName:t.toolName}),this.callbacks.sendPermissionCard({eventId:i.eventId,sessionId:i.sessionId,toolCallId:n,toolName:t.toolName,toolTitle:t.toolTitle,toolInput:a,options:t.options}))):(r.info("acp-adapter",`Permission request without active run, auto-approving: ${t.toolName}`),this.acpClient.respondPermission(e.requestId,{behavior:"allow"}),this.pendingApprovals.delete(n),this.captureAuditEvent({type:f.PermissionResponse,requestId:e.requestId,toolCallId:n,permissionDecision:"allow"}))}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?ke:Re),s=e.lastProgressAt+T-Date.now(),n=Math.max(0,Math.min(i,s));e.idleTimer=setTimeout(()=>{this.onIdleTimeout(e)},n)}async onIdleTimeout(e){if(this.activeRun?.eventId!==e.eventId)return;const t=this.acpClient,i=Date.now()-e.lastProgressAt;if(i>=T){if(t?.isAlive){const s=e.lastProgressAt,n=await this.checkSessionFileActivity(t.sessionId,e.lastIdleCheckAt);if(this.activeRun?.eventId!==e.eventId)return;const a=e.lastProgressAt>s;if(e.lastIdleCheckAt=Date.now(),a||n){n&&(e.lastProgressAt=Date.now()),e.idleNoProgressCount=0,r.info("acp-adapter",`Hard cap recheck found ${a?"stream progress":"session file activity"}, refreshing watchdog: ${e.eventId}`),this.resetIdleTimer(e);return}}r.error("acp-adapter",`Turn inactive ${Math.round(i/1e3)}s \u2265 cap ${Math.round(T/1e3)}s, forcing timeout: ${e.eventId}`),this.finishRun("failed",`turn inactivity timeout after ${Math.round(i/1e3)}s with no ACP activity`),this.declareStuck();return}t?.isAlive?t.ping(5e3).then(async s=>{if(this.activeRun?.eventId!==e.eventId)return;if(!s){await this.handlePingFailure(e,t);return}if(e.consecutivePingFailures=0,e.awaitingToolResult){const d=e.lastIdleCheckAt,h=await this.checkSessionFileActivity(t.sessionId,d);if(this.activeRun?.eventId!==e.eventId)return;e.lastIdleCheckAt=Date.now(),h?(e.lastProgressAt=Date.now(),r.info("acp-adapter",`Idle timer: tool running, ping ok, session file active, refreshing watchdog: ${e.eventId}`)):r.info("acp-adapter",`Idle timer: tool running, ping ok, continuing until inactivity cap: ${e.eventId}`),e.idleNoProgressCount=0,this.resetIdleTimer(e);return}const n=e.lastIdleCheckAt,a=e.lastProgressAt>n,o=a?!1:await this.checkSessionFileActivity(t.sessionId,n);if(this.activeRun?.eventId!==e.eventId)return;const l=a||o;e.lastIdleCheckAt=Date.now(),l?(o&&(r.info("acp-adapter",`Idle timer: no stream progress but session file active, continuing: ${e.eventId}`),e.lastProgressAt=Date.now()),e.idleNoProgressCount=0,this.resetIdleTimer(e)):(e.idleNoProgressCount++,r.info("acp-adapter",`Idle timer: ping ok, no stream progress (cycle=${e.idleNoProgressCount}), extending until inactivity cap (${Math.round(T/6e4)}min): ${e.eventId}`),this.resetIdleTimer(e))}).catch(()=>{this.activeRun?.eventId===e.eventId&&this.handlePingFailure(e,t)}):(r.error("acp-adapter",`Agent idle (no client), declaring stuck: ${e.eventId}`),this.finishRun("failed",this.describeUnreachable()),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||this.pendingQuestions.size>0){const a=s?"stream progress":n?"session file activity":e.awaitingToolResult?"tool in flight":"question pending user answer";r.info("acp-adapter",`Idle timer: ping failed but ${a}, continuing: ${e.eventId}`),e.consecutivePingFailures=0,e.lastIdleCheckAt=Date.now(),n&&(e.lastProgressAt=Date.now()),this.resetIdleTimer(e);return}if(e.consecutivePingFailures++,e.lastIdleCheckAt=Date.now(),e.consecutivePingFailures<j){r.warn("acp-adapter",`Idle timer: ping failed (${e.consecutivePingFailures}/${j}), rechecking in ${L/1e3}s: ${e.eventId}`),this.resetIdleTimer(e,L);return}r.error("acp-adapter",`Ping failed ${e.consecutivePingFailures}x with no activity evidence, declaring stuck: ${e.eventId}`),this.finishRun("failed",this.describeUnreachable()),this.declareStuck()}}async checkSessionFileActivity(e,t){if(!e)return!1;try{if(this.isKimi)return await Ce(e,t);const i=C(this.config.command);return i==="kiro-cli"?await Ae(e,t):i==="qodercli"?await F(".qoder",e,t):i==="qoderclicn"?await F(".qoder-cn",e,t):i==="dim"?await Se(e,t):!1}catch{return!1}}shouldDeferSyntheticResultForDetachedKimiTask(e,t){if(!this.isKimi)return!1;try{return be(t.sessionId||this.acpClient?.sessionId||e.sessionId)}catch{return!1}}declareStuck(){this.pendingQuestions.clear(),this.acpClient&&(this.acpClient.dispose(),this.acpClient.removeAllListeners(),this.acpClient=null),this.agentProcess&&(this.agentProcess.close().catch(()=>{}),this.agentProcess=null),this.emit("stuck")}appendToStream(e,t){e.buffer+=t,e.flushTimer||(e.flushTimer=setTimeout(()=>this.flushStream(),we))}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>=Me?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()},$e),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 S;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+=G)this.callbacks.sendAgentMessage(t,i.slice(s,s+G))}}suppressOrphanOutput(e){this.orphanSuppressedUntil=Date.now()+e,this.orphanFlushTimer&&(clearTimeout(this.orphanFlushTimer),this.orphanFlushTimer=null),this.orphanBuffer="",this.orphanQuotedStream=new S,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)}lastExitCode=null;describeUnreachable(){return this.agentProcess?.alive?"agent unreachable: no response while its process is still running":this.lastExitCode===null?"agent unreachable: its process is no longer running":`agent unreachable: its process exited (code=${this.lastExitCode})`}static sanitizeFailureReasonForUser(e){return e.replace(/\bunreachable\b/gi,"unresponsive").replace(/\bidle cycles?\b/gi,"consecutive checks")}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);let n=!1;if(t)if(e==="failed"){r.error("acp-adapter",`finishRun failed: ${t} event=${i.eventId}`);const o=z.sanitizeFailureReasonForUser(t);i.buffer+=`
19
19
 
20
- The task was interrupted (${a}). Please resend your instruction.`,n=!0}else o.info("acp-adapter",`finishRun ${e}: ${t} event=${i.eventId}`);i.buffer&&(this.emitSegmentedStream(i,i.buffer),i.buffer="");const r=++i.chunkSeq;this.callbacks.sendFinalStreamChunkReliable?this.callbacks.sendFinalStreamChunkReliable(i.eventId,i.sessionId,i.currentClientMsgId).then(()=>{i.silent||this.callbacks.sendEventResult(i.eventId,e,t,void 0,n),this.persistEventResult(i,e,t),this.replayNextDeferredEvent(),this.tryRunPendingAutoCompact()}).catch(a=>{const l=`final output delivery failed: ${a instanceof Error?a.message:String(a)}`,d=e==="responded"?"failed":e,h=e==="responded"?l:t,u=e==="responded"?!1:n;o.warn("acp-adapter",`finalStreamChunk rejected/failed event=${i.eventId}: ${l}`),i.silent||this.callbacks.sendEventResult(i.eventId,d,h,void 0,u),this.persistEventResult(i,d,h),this.replayNextDeferredEvent(),this.tryRunPendingAutoCompact()}):(this.callbacks.sendStreamChunk(i.eventId,i.sessionId,"",r,!0,i.currentClientMsgId),i.silent||this.callbacks.sendEventResult(i.eventId,e,t,void 0,n),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=E(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=E(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<K||this.pendingAutoCompact||this.compacting||(this.pendingAutoCompact=!0,o.info("acp-adapter",`[auto-compact] context_window usedPercentage=${t.toFixed(1)}% >= ${K}%, 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=>{o.info("acp-adapter",`[auto-compact] compact done status=${e.status} msg=${e.message??""}`)}).catch(e=>{o.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 Oe extends ${adapterSessionId;constructor(e){super(),this.adapterSessionId=e}emitDone(e){this.emit("done",e)}emitError(e){if(this.listenerCount("error")===0){o.warn("acp-adapter",`Prompt handle error (no listeners): ${e.message}`);return}this.emit("error",e)}async cancel(){}}export{z as AcpAdapter};
20
+ The task was interrupted (${o}). Please resend your instruction.`,n=!0}else r.info("acp-adapter",`finishRun ${e}: ${t} event=${i.eventId}`);i.buffer&&(this.emitSegmentedStream(i,i.buffer),i.buffer="");const a=++i.chunkSeq;this.callbacks.sendFinalStreamChunkReliable?this.callbacks.sendFinalStreamChunkReliable(i.eventId,i.sessionId,i.currentClientMsgId).then(()=>{i.silent||this.callbacks.sendEventResult(i.eventId,e,t,void 0,n),this.persistEventResult(i,e,t),this.replayNextDeferredEvent(),this.tryRunPendingAutoCompact()}).catch(o=>{const l=`final output delivery failed: ${o instanceof Error?o.message:String(o)}`,d=e==="responded"?"failed":e,h=e==="responded"?l:t,u=e==="responded"?!1:n;r.warn("acp-adapter",`finalStreamChunk rejected/failed event=${i.eventId}: ${l}`),i.silent||this.callbacks.sendEventResult(i.eventId,d,h,void 0,u),this.persistEventResult(i,d,h),this.replayNextDeferredEvent(),this.tryRunPendingAutoCompact()}):(this.callbacks.sendStreamChunk(i.eventId,i.sessionId,"",a,!0,i.currentClientMsgId),i.silent||this.callbacks.sendEventResult(i.eventId,e,t,void 0,n),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=E(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=E(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<K||this.pendingAutoCompact||this.compacting||(this.pendingAutoCompact=!0,r.info("acp-adapter",`[auto-compact] context_window usedPercentage=${t.toFixed(1)}% >= ${K}%, 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 Oe extends ${adapterSessionId;constructor(e){super(),this.adapterSessionId=e}emitDone(e){this.emit("done",e)}emitError(e){if(this.listenerCount("error")===0){r.warn("acp-adapter",`Prompt handle error (no listeners): ${e.message}`);return}this.emit("error",e)}async cancel(){}}export{z as AcpAdapter};
@@ -1 +1 @@
1
- import c from"node:http";import{randomUUID as d}from"node:crypto";import{log as o}from"../../core/log/index.js";function l(t){t.writeHead(401,{"content-type":"application/json"}),t.end(JSON.stringify({error:"unauthorized"}))}function u(t){t.writeHead(404,{"content-type":"application/json"}),t.end(JSON.stringify({error:"not_found"}))}function h(t,e){t.writeHead(400,{"content-type":"application/json"}),t.end(JSON.stringify({error:e}))}function p(t,e={ok:!0}){t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify(e))}async function v(t){const e=[];for await(const n of t)e.push(n);const r=Buffer.concat(e).toString("utf8").trim();return r?JSON.parse(r):{}}function k(t){const e=(t.headers.authorization??"").trim();return e.toLowerCase().startsWith("bearer ")?e.slice(7).trim():""}class w{host="127.0.0.1";port=0;token;callbacks;server=null;address=null;constructor(e){this.token=d(),this.callbacks=e}getToken(){return this.token}getURL(){return this.address?`http://${this.address.address}:${this.address.port}`:""}async start(){this.server||(this.server=c.createServer(async(e,r)=>{try{await this.handleRequest(e,r)}catch(n){h(r,n instanceof Error?n.message:String(n))}}),await new Promise((e,r)=>{this.server.once("error",r),this.server.listen(this.port,this.host,()=>{this.server.off("error",r),e()})}),this.address=this.server.address(),o.info("claude-bridge",`Bridge server listening on ${this.getURL()}`))}async stop(){if(!this.server)return;const e=this.server;this.server=null,this.address=null,e.closeIdleConnections?.(),e.closeAllConnections?.(),await new Promise((r,n)=>{e.close(s=>s?n(s):r())})}async handleRequest(e,r){if(k(e)!==this.token){l(r);return}if(e.method!=="POST"){r.writeHead(405,{"content-type":"application/json"}),r.end(JSON.stringify({error:"method_not_allowed"}));return}const n=new URL(e.url,"http://localhost").pathname,s=await v(e),i=f.get(n);if(!i){u(r);return}const a=await i(this.callbacks,s);p(r,a??{ok:!0})}}const f=new Map([["/v1/worker/register",async(t,e)=>(o.info("claude-bridge",`Worker registered: ${e.worker_id} (pid=${e.pid})`),t.onRegisterWorker(e))],["/v1/worker/status",async(t,e)=>(o.info("claude-bridge",`Worker status: ${e.status}`),t.onStatusUpdate(e))],["/v1/worker/send-text",async(t,e)=>t.onSendText(e)],["/v1/worker/send-stream-chunk",async(t,e)=>t.onSendStreamChunk(e)],["/v1/worker/send-media",async(t,e)=>t.onSendMedia(e)],["/v1/worker/delete-message",async(t,e)=>t.onDeleteMessage(e)],["/v1/worker/ack-event",async(t,e)=>t.onAckEvent(e)],["/v1/worker/event-result",async(t,e)=>t.onSendEventResult(e)],["/v1/worker/event-stop-ack",async(t,e)=>t.onSendEventStopAck(e)],["/v1/worker/event-stop-result",async(t,e)=>t.onSendEventStopResult(e)],["/v1/worker/session-composing",async(t,e)=>t.onSetSessionComposing(e)],["/v1/worker/agent-invoke",async(t,e)=>t.onAgentInvoke(e)],["/v1/worker/local-action-result",async(t,e)=>t.onLocalActionResult(e)]]);export{w as ClaudeBridgeServer};
1
+ import c from"node:http";import{randomUUID as d}from"node:crypto";import{log as o}from"../../core/log/index.js";function l(t){t.writeHead(401,{"content-type":"application/json"}),t.end(JSON.stringify({error:"unauthorized"}))}function u(t){t.writeHead(404,{"content-type":"application/json"}),t.end(JSON.stringify({error:"not_found"}))}function h(t,e){t.writeHead(400,{"content-type":"application/json"}),t.end(JSON.stringify({error:e}))}function p(t,e={ok:!0}){t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify(e))}async function v(t){const e=[];for await(const r of t)e.push(r);const n=Buffer.concat(e).toString("utf8").trim();return n?JSON.parse(n):{}}function k(t){const e=(t.headers.authorization??"").trim();return e.toLowerCase().startsWith("bearer ")?e.slice(7).trim():""}class w{host="127.0.0.1";port=0;token;callbacks;server=null;address=null;constructor(e){this.token=d(),this.callbacks=e}getToken(){return this.token}getURL(){return this.address?`http://${this.address.address}:${this.address.port}`:""}async start(){this.server||(this.server=c.createServer(async(e,n)=>{try{await this.handleRequest(e,n)}catch(r){h(n,r instanceof Error?r.message:String(r))}}),await new Promise((e,n)=>{this.server.once("error",n),this.server.listen(this.port,this.host,()=>{this.server.off("error",n),e()})}),this.address=this.server.address(),o.info("claude-bridge",`Bridge server listening on ${this.getURL()}`))}async stop(){if(!this.server)return;const e=this.server;this.server=null,this.address=null,e.closeIdleConnections?.(),e.closeAllConnections?.(),await new Promise((n,r)=>{e.close(s=>s?r(s):n())})}async handleRequest(e,n){if(k(e)!==this.token){l(n);return}if(e.method!=="POST"){n.writeHead(405,{"content-type":"application/json"}),n.end(JSON.stringify({error:"method_not_allowed"}));return}const r=new URL(e.url,"http://localhost").pathname,s=await v(e),i=f.get(r);if(!i){u(n);return}const a=await i(this.callbacks,s);p(n,a??{ok:!0})}}const f=new Map([["/v1/worker/register",async(t,e)=>(o.info("claude-bridge",`Worker registered: ${e.worker_id} (pid=${e.pid})`),t.onRegisterWorker(e))],["/v1/worker/status",async(t,e)=>(o.info("claude-bridge",`Worker status: ${e.status}`),t.onStatusUpdate(e))],["/v1/worker/send-text",async(t,e)=>t.onSendText(e)],["/v1/worker/send-stream-chunk",async(t,e)=>t.onSendStreamChunk(e)],["/v1/worker/send-media",async(t,e)=>t.onSendMedia(e)],["/v1/worker/delete-message",async(t,e)=>t.onDeleteMessage(e)],["/v1/worker/ack-event",async(t,e)=>t.onAckEvent(e)],["/v1/worker/event-result",async(t,e)=>t.onSendEventResult(e)],["/v1/worker/event-stop-ack",async(t,e)=>t.onSendEventStopAck(e)],["/v1/worker/event-stop-result",async(t,e)=>t.onSendEventStopResult(e)],["/v1/worker/session-composing",async(t,e)=>t.onSetSessionComposing(e)],["/v1/worker/agent-invoke",async(t,e)=>t.onAgentInvoke(e)],["/v1/worker/local-action-result",async(t,e)=>t.onLocalActionResult(e)]]);export{w as ClaudeBridgeServer};
@@ -1 +1 @@
1
- import{randomUUID as x}from"node:crypto";import{CallToolRequestSchema as S,ListToolsRequestSchema as w}from"@modelcontextprotocol/sdk/types.js";import{log as f}from"../../core/log/index.js";import{toolCallToInvoke as k}from"../../core/mcp/tools.js";const E=new Set(["contact_search","session_search","message_history","message_search","group_create","group_detail_read","group_leave_self","group_member_add","group_member_remove","group_member_role_update","group_all_members_muted_update","group_member_speaking_update","group_dissolve","send_msg","delete_msg","agent_api_create","agent_category_list","agent_category_create","agent_category_update","agent_category_assign","agent_api_key_rotate"]),y=3e4,I=[{name:"reply",description:"Send a visible message back to the chat for this grix-claude event.",inputSchema:{type:"object",properties:{text:{type:"string",description:"The visible reply text to send."},chat_id:{type:"string",description:"The target chat/session id from the <channel> tag."},event_id:{type:"string",description:"The Aibot event_id from the <channel> tag."},reply_to:{type:"string",description:"Optional message_id to quote instead of the inbound trigger message."},files:{type:"array",items:{type:"string"},description:"Optional absolute local file paths. Each file is uploaded through Agent API OSS presign before sending."},final:{type:"boolean",description:"Whether this is the final reply for the event. Defaults to false \u2014 the event stays open while Claude continues working, and auto-completes after inactivity. Set true only when this is definitively the last message for the event."}},required:["chat_id","event_id"]}},{name:"complete",description:"Finish an event without sending a visible reply so the backend does not time out.",inputSchema:{type:"object",properties:{event_id:{type:"string",description:"The Aibot event_id from the <channel> tag."},status:{type:"string",enum:["responded","canceled","failed"]},code:{type:"string"},msg:{type:"string"}},required:["event_id","status"]}},{name:"delete_message",description:"Delete a previously sent message in the same grix-claude chat.",inputSchema:{type:"object",properties:{chat_id:{type:"string"},message_id:{type:"string"}},required:["chat_id","message_id"]}},{name:"status",description:"Show grix-claude runtime status, upstream access state, bridge health, and startup hints.",inputSchema:{type:"object",properties:{}}},{name:"send",description:"Send a message to a chat session proactively, without requiring an inbound event. Use for notifications or scheduled reports.",inputSchema:{type:"object",properties:{chat_id:{type:"string",description:"The target chat/session id."},text:{type:"string",description:"The message text to send."}},required:["chat_id","text"]}},{name:"access_pair",description:"Forward a sender pairing approval code to upstream access control.",inputSchema:{type:"object",properties:{code:{type:"string"}},required:["code"]}},{name:"access_deny",description:"Forward a sender pairing denial code to upstream access control.",inputSchema:{type:"object",properties:{code:{type:"string"}},required:["code"]}},{name:"allow_sender",description:"Ask upstream access control to allow a sender_id.",inputSchema:{type:"object",properties:{sender_id:{type:"string"}},required:["sender_id"]}},{name:"remove_sender",description:"Ask upstream access control to remove a sender_id.",inputSchema:{type:"object",properties:{sender_id:{type:"string"}},required:["sender_id"]}},{name:"access_policy",description:"Ask upstream access control to update the sender access policy.",inputSchema:{type:"object",properties:{policy:{type:"string",enum:["allowlist","open","disabled"]}},required:["policy"]}},{name:"grix_query",description:"Search contacts, sessions, message history, or messages by keyword in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["contact_search","session_search","message_history","message_search"]},keyword:{type:"string"},id:{type:"string"},sessionId:{type:"string"},limit:{type:"number"},offset:{type:"number"},beforeId:{type:"string"}},required:["action"]}},{name:"grix_group",description:"Manage groups in the Grix/AIBot platform: create, get details, leave, dissolve, manage members and permissions.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["create","detail","leave","add_members","remove_members","update_member_role","update_all_members_muted","update_member_speaking","dissolve"]},sessionId:{type:"string"},name:{type:"string"},memberIds:{type:"array",items:{type:"string"}},memberTypes:{type:"array",items:{type:"integer",enum:[1,2]}},memberId:{type:"string"},role:{type:"integer",enum:[1,2]},memberType:{type:"integer",description:"Member type (for update_member_role / update_member_speaking)."},allMembersMuted:{type:"boolean"},isSpeakMuted:{type:"boolean"},canSpeakWhenAllMuted:{type:"boolean",description:"Allow speaking when all muted (for update_member_speaking)."}},required:["action"]}},{name:"grix_message_send",description:"Send a message to a session in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{sessionId:{type:"string"},content:{type:"string"},msgType:{type:"number"},quotedMessageId:{type:"string"},threadId:{type:"string"}},required:["sessionId","content"]}},{name:"grix_message_unsend",description:"Recall/unsend a message in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{sessionId:{type:"string"},msgId:{type:"string"}},required:["sessionId","msgId"]}},{name:"grix_admin",description:"Agent and category management in the Grix/AIBot platform: create agents, manage categories, rotate API keys.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["create_agent","list_categories","create_category","update_category","assign_category","rotate_api_key"]},agentId:{type:"string"},agentName:{type:"string"},introduction:{type:"string"},isMain:{type:"boolean"},categoryId:{type:"string"},name:{type:"string"},parentId:{type:"string"},sortOrder:{type:"number"}},required:["action"]}}];function q(n,e){n.setRequestHandler(w,async()=>({tools:I})),n.setRequestHandler(S,async i=>{const{name:r,arguments:t}=i.params,s=t??{};try{switch(r){case"reply":return await A(s,e);case"complete":return await $(s,e);case"delete_message":return await T(s,e);case"status":return j(e);case"send":return await M(s,e);case"access_pair":case"access_deny":case"allow_sender":case"remove_sender":case"access_policy":return await R(r,s,e);case"grix_query":case"grix_group":case"grix_message_send":case"grix_message_unsend":case"grix_admin":return await O(r,s,e);default:return{content:[{type:"text",text:`Unknown tool: ${r}`}],isError:!0}}}catch(a){return f.error("claude-tools",`Tool ${r} error: ${a}`),{content:[{type:"text",text:`Error: ${a instanceof Error?a.message:String(a)}`}],isError:!0}}})}async function A(n,e){const i=e.getActiveEvent();if(!i)return{content:[{type:"text",text:"No active event to reply to"}],isError:!0};const r=String(n.text??""),t=String(n.chat_id??""),s=String(n.event_id??i.eventId),a=n.reply_to,d=n.files,_=n.final===!0;if(!t||!s)return{content:[{type:"text",text:"reply requires chat_id and event_id"}],isError:!0};if(!r.trim()&&(!d||d.length===0))return{content:[{type:"text",text:"reply requires at least one of text or files"}],isError:!0};const{text:g,quotedMessageId:v}=e.resolveQuotedMessageId(a,r),b=[];let m=0;const h=`reply_${s}_${Date.now()}`;try{if(g){const p=e.splitText(g);for(let o=0;o<p.length;o++){if(!e.isEventActive(s))return c("ignored: event no longer active");m++,e.bridge.sendStreamChunk(s,t,p[o],++i.chunkSeq,!1,h)}}if(d&&d.length>0)for(const p of d){if(!e.isEventActive(s))return c("ignored: event no longer active");m++;const o=await e.uploadFile(p,t),l=`${x()}_${m}`;e.bridge.sendMedia(s,t,o.access_url,o.file_name,v,l,o.extra),f.info("claude-tools",`File sent: ${o.file_name}`)}e.bridge.sendStreamChunk(s,t,"",++i.chunkSeq,!0,h)}catch(p){if(g&&b.length===0)try{const o=`fallback_${s}_${Date.now()}`,l=e.splitText(g);for(let u=0;u<l.length;u++)e.bridge.sendStreamChunk("",t,l[u],u+1,!1,o);return e.bridge.sendStreamChunk("",t,"",l.length+1,!0,o),e.markReplySent(s),_&&e.finalizeEvent(s,"responded"),c("sent via fallback")}catch{}if(!e.isEventActive(s))return c("ignored: event no longer active");throw e.bridge.sendEventResult(s,"failed",String(p),"send_msg_failed"),p}return e.markReplySent(s),_?e.finalizeEvent(s,"responded"):(i.responded=!0,e.clearActiveEvent("completed")),c("Reply sent")}async function $(n,e){const i=e.getActiveEvent(),r=String(n.event_id??""),t=n.status??"",s=n.code,a=n.msg;if(!r||!t)return{content:[{type:"text",text:"complete requires event_id and status"}],isError:!0};const d=["responded","canceled","failed"];return d.includes(t)?e.isEventActive(r)?(e.bridge.sendEventResult(r,t,a,s),e.clearActiveEvent(t),c("Event completed")):c("ignored: event no longer active"):{content:[{type:"text",text:`status must be one of: ${d.join(", ")}`}],isError:!0}}async function T(n,e){const i=String(n.chat_id??""),r=String(n.message_id??"");if(!i||!r)return{content:[{type:"text",text:"chat_id and message_id are required"}],isError:!0};try{return await e.bridge.agentInvoke("grix_message_unsend",{sessionId:i,msgId:r},y),c(`deleted (${r})`)}catch(t){return{content:[{type:"text",text:`Delete failed: ${t}`}],isError:!0}}}function j(n){const e=n.getStatusInfo();return{content:[{type:"text",text:JSON.stringify({alive:e.alive,active_event:e.activeEvent,pending_approvals:e.pendingPermissions,pending_questions:e.pendingElicitations})}]}}async function M(n,e){const i=String(n.chat_id??""),r=String(n.text??"");if(!i||!r)return{content:[{type:"text",text:"chat_id and text are required"}],isError:!0};try{const t=e.splitText(r),s=`send_${i}_${Date.now()}`;for(let a=0;a<t.length;a++)e.bridge.sendStreamChunk("",i,t[a],a+1,!1,s);return e.bridge.sendStreamChunk("",i,"",t.length+1,!0,s),c("sent")}catch(t){return{content:[{type:"text",text:`Send failed: ${t}`}],isError:!0}}}const C={access_pair:{verb:"pair_approve",payloadKey:"code"},access_deny:{verb:"pair_deny",payloadKey:"code"},allow_sender:{verb:"sender_allow",payloadKey:"sender_id"},remove_sender:{verb:"sender_remove",payloadKey:"sender_id"},access_policy:{verb:"policy_set",payloadKey:"policy"}};async function R(n,e,i){try{const r=C[n];if(!r)throw new Error(`Unknown access control tool: ${n}`);const t={};e.code!=null&&(t.code=e.code),e.sender_id!=null&&(t.sender_id=e.sender_id),e.policy!=null&&(t.policy=e.policy);const s=await i.bridge.agentInvoke("claude_access_control",{verb:r.verb,payload:t},y);return{content:[{type:"text",text:typeof s=="string"?s:JSON.stringify(s)}]}}catch(r){return{content:[{type:"text",text:`${n} failed: ${r}`}],isError:!0}}}async function O(n,e,i){try{const r=k(n,e);if(!E.has(r.action))throw new Error(`Action not allowed: ${r.action}`);const t=await i.bridge.agentInvoke(r.action,r.params,y);if(t&&Number(t.code??0)!==0)throw new Error(String(t.msg??"invoke failed"));return{content:[{type:"text",text:t?.data!=null?typeof t.data=="string"?t.data:JSON.stringify(t.data):JSON.stringify(t)}]}}catch(r){return{content:[{type:"text",text:`${n} failed: ${r}`}],isError:!0}}}function c(n){return{content:[{type:"text",text:n}]}}export{q as registerClaudeTools};
1
+ import{randomUUID as x}from"node:crypto";import{CallToolRequestSchema as S,ListToolsRequestSchema as w}from"@modelcontextprotocol/sdk/types.js";import{log as f}from"../../core/log/index.js";import{toolCallToInvoke as k}from"../../core/mcp/tools.js";const E=new Set(["contact_search","session_search","message_history","message_search","group_create","group_detail_read","group_leave_self","group_member_add","group_member_remove","group_member_role_update","group_all_members_muted_update","group_member_speaking_update","group_dissolve","send_msg","delete_msg","agent_api_create","agent_category_list","agent_category_create","agent_category_update","agent_category_assign","agent_api_key_rotate"]),y=3e4,I=[{name:"reply",description:"Send a visible message back to the chat for this grix-claude event.",inputSchema:{type:"object",properties:{text:{type:"string",description:"The visible reply text to send."},chat_id:{type:"string",description:"The target chat/session id from the <channel> tag."},event_id:{type:"string",description:"The Aibot event_id from the <channel> tag."},reply_to:{type:"string",description:"Optional message_id to quote instead of the inbound trigger message."},files:{type:"array",items:{type:"string"},description:"Optional absolute local file paths. Each file is uploaded through Agent API OSS presign before sending."},final:{type:"boolean",description:"Whether this is the final reply for the event. Defaults to false \u2014 the event stays open while Claude continues working, and auto-completes after inactivity. Set true only when this is definitively the last message for the event."}},required:["chat_id","event_id"]}},{name:"complete",description:"Finish an event without sending a visible reply so the backend does not time out.",inputSchema:{type:"object",properties:{event_id:{type:"string",description:"The Aibot event_id from the <channel> tag."},status:{type:"string",enum:["responded","canceled","failed"]},code:{type:"string"},msg:{type:"string"}},required:["event_id","status"]}},{name:"delete_message",description:"Delete a previously sent message in the same grix-claude chat.",inputSchema:{type:"object",properties:{chat_id:{type:"string"},message_id:{type:"string"}},required:["chat_id","message_id"]}},{name:"status",description:"Show grix-claude runtime status, upstream access state, bridge health, and startup hints.",inputSchema:{type:"object",properties:{}}},{name:"send",description:"Send a message to a chat session proactively, without requiring an inbound event. Use for notifications or scheduled reports.",inputSchema:{type:"object",properties:{chat_id:{type:"string",description:"The target chat/session id."},text:{type:"string",description:"The message text to send."}},required:["chat_id","text"]}},{name:"access_pair",description:"Forward a sender pairing approval code to upstream access control.",inputSchema:{type:"object",properties:{code:{type:"string"}},required:["code"]}},{name:"access_deny",description:"Forward a sender pairing denial code to upstream access control.",inputSchema:{type:"object",properties:{code:{type:"string"}},required:["code"]}},{name:"allow_sender",description:"Ask upstream access control to allow a sender_id.",inputSchema:{type:"object",properties:{sender_id:{type:"string"}},required:["sender_id"]}},{name:"remove_sender",description:"Ask upstream access control to remove a sender_id.",inputSchema:{type:"object",properties:{sender_id:{type:"string"}},required:["sender_id"]}},{name:"access_policy",description:"Ask upstream access control to update the sender access policy.",inputSchema:{type:"object",properties:{policy:{type:"string",enum:["allowlist","open","disabled"]}},required:["policy"]}},{name:"grix_query",description:"Search contacts, sessions, message history, or messages by keyword in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["contact_search","session_search","message_history","message_search"]},keyword:{type:"string"},id:{type:"string"},sessionId:{type:"string"},limit:{type:"number"},offset:{type:"number"},beforeId:{type:"string"}},required:["action"]}},{name:"grix_group",description:"Manage groups in the Grix/AIBot platform: create, get details, leave, dissolve, manage members and permissions.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["create","detail","leave","add_members","remove_members","update_member_role","update_all_members_muted","update_member_speaking","dissolve"]},sessionId:{type:"string"},name:{type:"string"},memberIds:{type:"array",items:{type:"string"}},memberTypes:{type:"array",items:{type:"integer",enum:[1,2]}},memberId:{type:"string"},role:{type:"integer",enum:[1,2]},memberType:{type:"integer",description:"Member type (for update_member_role / update_member_speaking)."},allMembersMuted:{type:"boolean"},isSpeakMuted:{type:"boolean"},canSpeakWhenAllMuted:{type:"boolean",description:"Allow speaking when all muted (for update_member_speaking)."}},required:["action"]}},{name:"grix_message_send",description:"Send a message to a session in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{sessionId:{type:"string"},content:{type:"string"},msgType:{type:"number"},quotedMessageId:{type:"string"},threadId:{type:"string"}},required:["sessionId","content"]}},{name:"grix_message_unsend",description:"Recall/unsend a message in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{sessionId:{type:"string"},msgId:{type:"string"}},required:["sessionId","msgId"]}},{name:"grix_admin",description:"Agent and category management in the Grix/AIBot platform: create agents, manage categories, rotate API keys.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["create_agent","list_categories","create_category","update_category","assign_category","rotate_api_key"]},agentId:{type:"string"},agentName:{type:"string"},introduction:{type:"string"},isMain:{type:"boolean"},categoryId:{type:"string"},name:{type:"string"},parentId:{type:"string"},sortOrder:{type:"number"}},required:["action"]}}];function q(r,e){r.setRequestHandler(w,async()=>({tools:I})),r.setRequestHandler(S,async i=>{const{name:n,arguments:t}=i.params,s=t??{};try{switch(n){case"reply":return await A(s,e);case"complete":return await $(s,e);case"delete_message":return await T(s,e);case"status":return j(e);case"send":return await M(s,e);case"access_pair":case"access_deny":case"allow_sender":case"remove_sender":case"access_policy":return await R(n,s,e);case"grix_query":case"grix_group":case"grix_message_send":case"grix_message_unsend":case"grix_admin":return await O(n,s,e);default:return{content:[{type:"text",text:`Unknown tool: ${n}`}],isError:!0}}}catch(a){return f.error("claude-tools",`Tool ${n} error: ${a}`),{content:[{type:"text",text:`Error: ${a instanceof Error?a.message:String(a)}`}],isError:!0}}})}async function A(r,e){const i=e.getActiveEvent();if(!i)return{content:[{type:"text",text:"No active event to reply to"}],isError:!0};const n=String(r.text??""),t=String(r.chat_id??""),s=String(r.event_id??i.eventId),a=r.reply_to,d=r.files,_=r.final===!0;if(!t||!s)return{content:[{type:"text",text:"reply requires chat_id and event_id"}],isError:!0};if(!n.trim()&&(!d||d.length===0))return{content:[{type:"text",text:"reply requires at least one of text or files"}],isError:!0};const{text:g,quotedMessageId:v}=e.resolveQuotedMessageId(a,n),b=[];let m=0;const h=`reply_${s}_${Date.now()}`;try{if(g){const p=e.splitText(g);for(let o=0;o<p.length;o++){if(!e.isEventActive(s))return c("ignored: event no longer active");m++,e.bridge.sendStreamChunk(s,t,p[o],++i.chunkSeq,!1,h)}}if(d&&d.length>0)for(const p of d){if(!e.isEventActive(s))return c("ignored: event no longer active");m++;const o=await e.uploadFile(p,t),l=`${x()}_${m}`;e.bridge.sendMedia(s,t,o.access_url,o.file_name,v,l,o.extra),f.info("claude-tools",`File sent: ${o.file_name}`)}e.bridge.sendStreamChunk(s,t,"",++i.chunkSeq,!0,h)}catch(p){if(g&&b.length===0)try{const o=`fallback_${s}_${Date.now()}`,l=e.splitText(g);for(let u=0;u<l.length;u++)e.bridge.sendStreamChunk("",t,l[u],u+1,!1,o);return e.bridge.sendStreamChunk("",t,"",l.length+1,!0,o),e.markReplySent(s),_&&e.finalizeEvent(s,"responded"),c("sent via fallback")}catch{}if(!e.isEventActive(s))return c("ignored: event no longer active");throw e.bridge.sendEventResult(s,"failed",String(p),"send_msg_failed"),p}return e.markReplySent(s),_?e.finalizeEvent(s,"responded"):(i.responded=!0,e.clearActiveEvent("completed")),c("Reply sent")}async function $(r,e){const i=e.getActiveEvent(),n=String(r.event_id??""),t=r.status??"",s=r.code,a=r.msg;if(!n||!t)return{content:[{type:"text",text:"complete requires event_id and status"}],isError:!0};const d=["responded","canceled","failed"];return d.includes(t)?e.isEventActive(n)?(e.bridge.sendEventResult(n,t,a,s),e.clearActiveEvent(t),c("Event completed")):c("ignored: event no longer active"):{content:[{type:"text",text:`status must be one of: ${d.join(", ")}`}],isError:!0}}async function T(r,e){const i=String(r.chat_id??""),n=String(r.message_id??"");if(!i||!n)return{content:[{type:"text",text:"chat_id and message_id are required"}],isError:!0};try{return await e.bridge.agentInvoke("grix_message_unsend",{sessionId:i,msgId:n},y),c(`deleted (${n})`)}catch(t){return{content:[{type:"text",text:`Delete failed: ${t}`}],isError:!0}}}function j(r){const e=r.getStatusInfo();return{content:[{type:"text",text:JSON.stringify({alive:e.alive,active_event:e.activeEvent,pending_approvals:e.pendingPermissions,pending_questions:e.pendingElicitations})}]}}async function M(r,e){const i=String(r.chat_id??""),n=String(r.text??"");if(!i||!n)return{content:[{type:"text",text:"chat_id and text are required"}],isError:!0};try{const t=e.splitText(n),s=`send_${i}_${Date.now()}`;for(let a=0;a<t.length;a++)e.bridge.sendStreamChunk("",i,t[a],a+1,!1,s);return e.bridge.sendStreamChunk("",i,"",t.length+1,!0,s),c("sent")}catch(t){return{content:[{type:"text",text:`Send failed: ${t}`}],isError:!0}}}const C={access_pair:{verb:"pair_approve",payloadKey:"code"},access_deny:{verb:"pair_deny",payloadKey:"code"},allow_sender:{verb:"sender_allow",payloadKey:"sender_id"},remove_sender:{verb:"sender_remove",payloadKey:"sender_id"},access_policy:{verb:"policy_set",payloadKey:"policy"}};async function R(r,e,i){try{const n=C[r];if(!n)throw new Error(`Unknown access control tool: ${r}`);const t={};e.code!=null&&(t.code=e.code),e.sender_id!=null&&(t.sender_id=e.sender_id),e.policy!=null&&(t.policy=e.policy);const s=await i.bridge.agentInvoke("claude_access_control",{verb:n.verb,payload:t},y);return{content:[{type:"text",text:typeof s=="string"?s:JSON.stringify(s)}]}}catch(n){return{content:[{type:"text",text:`${r} failed: ${n}`}],isError:!0}}}async function O(r,e,i){try{const n=k(r,e);if(!E.has(n.action))throw new Error(`Action not allowed: ${n.action}`);const t=await i.bridge.agentInvoke(n.action,n.params,y);if(t&&Number(t.code??0)!==0)throw new Error(String(t.msg??"invoke failed"));return{content:[{type:"text",text:t?.data!=null?typeof t.data=="string"?t.data:JSON.stringify(t.data):JSON.stringify(t)}]}}catch(n){return{content:[{type:"text",text:`${r} failed: ${n}`}],isError:!0}}}function c(r){return{content:[{type:"text",text:r}]}}export{q as registerClaudeTools};
@@ -1 +1 @@
1
- import{log as l}from"../../core/log/index.js";class c{controlURL="";token="";isConfigured(){return!!(this.controlURL&&this.token)}configure(t,e){this.controlURL=t.replace(/\/+$/,""),this.token=e.trim(),l.info("claude-worker-client",`Configured with control URL: ${this.controlURL}`)}async post(t,e,s){if(!this.isConfigured())throw new Error("worker control not configured");const i=new AbortController,o=setTimeout(()=>i.abort(),s);try{const r=await fetch(`${this.controlURL}${t}`,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${this.token}`},body:JSON.stringify(e),signal:i.signal}),n=await r.text(),a=n.trim()?JSON.parse(n):{};if(!r.ok)throw new Error(a.error||`worker control failed ${r.status}`);return a}finally{clearTimeout(o)}}isRetryableError(t){const e=t instanceof Error?t.message:String(t);return/fetch failed|network|ECONNRESET|ETIMEDOUT|EAI_AGAIN|socket hang up|aborted/i.test(e)}async postWithRetry(t,e,s,i=1){let o;for(let r=0;r<=i;r++)try{return r>0&&l.info("claude-worker-client",`Retrying ${t} attempt=${r+1}`),await this.post(t,e,s)}catch(n){if(o=n,r>=i||!this.isRetryableError(n))break;await new Promise(a=>setTimeout(a,150))}throw o instanceof Error?o:new Error(String(o))}async deliverEvent(t){return this.postWithRetry("/v1/worker/deliver-event",{payload:t},1e4,1)}async deliverStop(t){return this.postWithRetry("/v1/worker/deliver-stop",{payload:t},1e4,1)}async deliverLocalAction(t){return this.postWithRetry("/v1/worker/deliver-local-action",{payload:t},1e4,1)}async ping(){try{return await this.postWithRetry("/v1/worker/ping",{},5e3,1),!0}catch{return!1}}}export{c as ClaudeWorkerClient};
1
+ import{log as l}from"../../core/log/index.js";class c{controlURL="";token="";isConfigured(){return!!(this.controlURL&&this.token)}configure(r,e){this.controlURL=r.replace(/\/+$/,""),this.token=e.trim(),l.info("claude-worker-client",`Configured with control URL: ${this.controlURL}`)}async post(r,e,s){if(!this.isConfigured())throw new Error("worker control not configured");const i=new AbortController,o=setTimeout(()=>i.abort(),s);try{const t=await fetch(`${this.controlURL}${r}`,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${this.token}`},body:JSON.stringify(e),signal:i.signal}),n=await t.text(),a=n.trim()?JSON.parse(n):{};if(!t.ok)throw new Error(a.error||`worker control failed ${t.status}`);return a}finally{clearTimeout(o)}}isRetryableError(r){const e=r instanceof Error?r.message:String(r);return/fetch failed|network|ECONNRESET|ETIMEDOUT|EAI_AGAIN|socket hang up|aborted/i.test(e)}async postWithRetry(r,e,s,i=1){let o;for(let t=0;t<=i;t++)try{return t>0&&l.info("claude-worker-client",`Retrying ${r} attempt=${t+1}`),await this.post(r,e,s)}catch(n){if(o=n,t>=i||!this.isRetryableError(n))break;await new Promise(a=>setTimeout(a,150))}throw o instanceof Error?o:new Error(String(o))}async deliverEvent(r){return this.postWithRetry("/v1/worker/deliver-event",{payload:r},1e4,1)}async deliverStop(r){return this.postWithRetry("/v1/worker/deliver-stop",{payload:r},1e4,1)}async deliverLocalAction(r){return this.postWithRetry("/v1/worker/deliver-local-action",{payload:r},1e4,1)}async ping(){try{return await this.postWithRetry("/v1/worker/ping",{},5e3,1),!0}catch{return!1}}}export{c as ClaudeWorkerClient};
@@ -1,2 +1,2 @@
1
- import{spawn as x,execSync as y}from"node:child_process";import{randomUUID as v}from"node:crypto";import{mkdir as S}from"node:fs/promises";import{readFileSync as C}from"node:fs";import{join as d}from"node:path";import{homedir as T,tmpdir as I}from"node:os";import{log as o}from"../../core/log/index.js";import{MCP_HTTP_CHANNEL_NAME as l}from"./protocol-contract.js";function P(e){let t=null,r=0,n=!1,i=!1;const a=v(),s=e.gatewayUrl??"http://127.0.0.1:19580/mcp";return{async start(){await F(e.command,s,e.env);const c=E(e.grix),u=[...e.args??[],"--name",`grix-mcp-${e.name}`,"--session-id",a];e.fullAuto&&u.push("--dangerously-skip-permissions"),u.push("--dangerously-load-development-channels",`server:${l}`,"--append-system-prompt",c);const f=d(I(),`grix-mcp-claude-${e.name}`);await S(f,{recursive:!0});const{expectPath:$,pidPath:g}=await M(f,e.command,u),_={...process.env,...e.env??{}};t=x("/usr/bin/expect",[$],{cwd:e.cwd,env:_,stdio:["ignore","pipe","pipe"],detached:!0}),o.info("mcp-http-launcher",`\u542F\u52A8 Claude: name=${e.name} cwd=${e.cwd} pid=${t.pid}`),r=await k(g),n=!0,o.info("mcp-http-launcher",`Claude \u5B50\u8FDB\u7A0B PID: ${r}`),t.on("exit",(m,p)=>{o.info("mcp-http-launcher",`Claude \u9000\u51FA: code=${m} signal=${p}`),n=!1,t=null,r=0,i||(o.info("mcp-http-launcher","3 \u79D2\u540E\u81EA\u52A8\u91CD\u542F..."),setTimeout(()=>{i||this.start().catch(w=>{o.error("mcp-http-launcher",`\u91CD\u542F\u5931\u8D25: ${w}`)})},3e3))}),t.stdout?.on("data",m=>{const p=m.toString().trim();p&&o.info("mcp-http-launcher",`[stdout] ${p.slice(0,300)}`)}),t.stderr?.on("data",m=>{const p=m.toString().trim();p&&o.info("mcp-http-launcher",`[stderr] ${p.slice(0,300)}`)})},async stop(){if(i=!0,n=!1,r>0)try{process.kill(r,"SIGTERM")}catch{}if(t?.pid){try{process.kill(-t.pid,"SIGTERM")}catch{}await new Promise(c=>{const u=setTimeout(()=>{if(r>0)try{process.kill(r,"SIGKILL")}catch{}if(t?.pid)try{process.kill(-t.pid,"SIGKILL")}catch{}c()},5e3);t?.once("exit",()=>{clearTimeout(u),c()})})}t=null,r=0},getStatus(){return{name:e.name,alive:n,pid:r}}}}function E(e){return["You are connected to a chat via the grix MCP server.",`On startup, immediately call grix_authorize with: agentId="${e.agentId}", apiKey="${e.apiKey}", wsUrl="${e.wsUrl}", clientType="${e.clientType}".`,"When you receive a <channel> message, you MUST respond by calling the grix_reply tool (or the grix_complete tool if no response is needed).","Never write your reply as plain text \u2014 it will NOT reach the user. Only the grix_reply tool delivers your response to the chat.","The <channel> message contains event_id and session_id \u2014 pass them to grix_reply."].join(" ")}async function F(e,t,r){const n=d(T(),".claude.json");let i=null;try{const s=C(n,"utf8");i=JSON.parse(s)?.mcpServers?.[l]??null}catch{}if(i&&String(i.type??"").trim()==="http"&&String(i.url??"").trim()===t)return;o.info("mcp-http-launcher",`\u6CE8\u518C MCP Server: ${l} -> ${t}`);const a={...process.env,...r??{}};try{y(`${e} mcp remove -s user ${l}`,{encoding:"utf8",timeout:1e4,env:a,stdio:"pipe"})}catch{}y(`${e} mcp add --scope user --transport http ${l} ${t}`,{encoding:"utf8",timeout:1e4,env:a,stdio:"pipe"})}async function M(e,t,r){const{writeFile:n}=await import("node:fs/promises"),i=d(e,"claude.pid"),a=d(e,"claude.expect"),s=["log_user 1","set timeout -1","set startup_prompt_armed 1",`set claude_command [list {${h(t)}}${r.map(c=>` {${h(c)}}`).join("")}]`,"spawn -noecho {*}$claude_command",`set pid_file [open {${h(i)}} w]`,"puts $pid_file [exp_pid -i $spawn_id]","close $pid_file","expect {"," -re {(?i)(Quick.*safety.*check|trust.*folder)} {",' if {$startup_prompt_armed} { send -- "1\\r"; after 300 }; exp_continue'," }"," -re {(?i)I am using this for local development} {",' if {$startup_prompt_armed} { send -- "1\\r"; after 300 }; exp_continue'," }"," -re {(?i)(Enter.*confirm|Press.*Enter|Hit.*Enter)} {",' if {$startup_prompt_armed} { send -- "\\r"; after 300 }; exp_continue'," }"," -re {Listening for channel} {"," set startup_prompt_armed 0"," after 1000",' send -- "Call grix_authorize now as instructed in your system prompt.\\r"'," }"," -re {bypass permissions} {"," set startup_prompt_armed 0"," after 1000",' send -- "Call grix_authorize now as instructed in your system prompt.\\r"'," }"," eof {}","}","expect eof",""];return await n(i,"","utf8"),await n(a,s.join(`
2
- `),"utf8"),{expectPath:a,pidPath:i}}function h(e){return e.replace(/[\\{}$\[\]"]/g,"\\$&")}async function k(e,t=1e4){const{readFile:r}=await import("node:fs/promises"),n=Math.ceil(t/100);for(let i=0;i<n;i++){try{const a=await r(e,"utf8"),s=parseInt(String(a).trim(),10);if(Number.isFinite(s)&&s>0)return s}catch{}await new Promise(a=>setTimeout(a,100))}return 0}export{P as createMcpHttpLauncher};
1
+ import{spawn as x,execSync as y}from"node:child_process";import{randomUUID as v}from"node:crypto";import{mkdir as S}from"node:fs/promises";import{readFileSync as C}from"node:fs";import{join as d}from"node:path";import{homedir as T,tmpdir as I}from"node:os";import{log as o}from"../../core/log/index.js";import{MCP_HTTP_CHANNEL_NAME as m}from"./protocol-contract.js";function P(t){let e=null,r=0,n=!1,i=!1;const a=v(),s=t.gatewayUrl??"http://127.0.0.1:19580/mcp";return{async start(){await F(t.command,s,t.env);const c=E(t.grix),u=[...t.args??[],"--name",`grix-mcp-${t.name}`,"--session-id",a];t.fullAuto&&u.push("--dangerously-skip-permissions"),u.push("--dangerously-load-development-channels",`server:${m}`,"--append-system-prompt",c);const f=d(I(),`grix-mcp-claude-${t.name}`);await S(f,{recursive:!0});const{expectPath:$,pidPath:g}=await M(f,t.command,u),_={...process.env,...t.env??{}};e=x("/usr/bin/expect",[$],{cwd:t.cwd,env:_,stdio:["ignore","pipe","pipe"],detached:!0}),o.info("mcp-http-launcher",`\u542F\u52A8 Claude: name=${t.name} cwd=${t.cwd} pid=${e.pid}`),r=await k(g),n=!0,o.info("mcp-http-launcher",`Claude \u5B50\u8FDB\u7A0B PID: ${r}`),e.on("exit",(l,p)=>{o.info("mcp-http-launcher",`Claude \u9000\u51FA: code=${l} signal=${p}`),n=!1,e=null,r=0,i||(o.info("mcp-http-launcher","3 \u79D2\u540E\u81EA\u52A8\u91CD\u542F..."),setTimeout(()=>{i||this.start().catch(w=>{o.error("mcp-http-launcher",`\u91CD\u542F\u5931\u8D25: ${w}`)})},3e3))}),e.stdout?.on("data",l=>{const p=l.toString().trim();p&&o.info("mcp-http-launcher",`[stdout] ${p.slice(0,300)}`)}),e.stderr?.on("data",l=>{const p=l.toString().trim();p&&o.info("mcp-http-launcher",`[stderr] ${p.slice(0,300)}`)})},async stop(){if(i=!0,n=!1,r>0)try{process.kill(r,"SIGTERM")}catch{}if(e?.pid){try{process.kill(-e.pid,"SIGTERM")}catch{}await new Promise(c=>{const u=setTimeout(()=>{if(r>0)try{process.kill(r,"SIGKILL")}catch{}if(e?.pid)try{process.kill(-e.pid,"SIGKILL")}catch{}c()},5e3);e?.once("exit",()=>{clearTimeout(u),c()})})}e=null,r=0},getStatus(){return{name:t.name,alive:n,pid:r}}}}function E(t){return["You are connected to a chat via the grix MCP server.",`On startup, immediately call grix_authorize with: agentId="${t.agentId}", apiKey="${t.apiKey}", wsUrl="${t.wsUrl}", clientType="${t.clientType}".`,"When you receive a <channel> message, you MUST respond by calling the grix_reply tool (or the grix_complete tool if no response is needed).","Never write your reply as plain text \u2014 it will NOT reach the user. Only the grix_reply tool delivers your response to the chat.","The <channel> message contains event_id and session_id \u2014 pass them to grix_reply."].join(" ")}async function F(t,e,r){const n=d(T(),".claude.json");let i=null;try{const s=C(n,"utf8");i=JSON.parse(s)?.mcpServers?.[m]??null}catch{}if(i&&String(i.type??"").trim()==="http"&&String(i.url??"").trim()===e)return;o.info("mcp-http-launcher",`\u6CE8\u518C MCP Server: ${m} -> ${e}`);const a={...process.env,...r??{}};try{y(`${t} mcp remove -s user ${m}`,{encoding:"utf8",timeout:1e4,env:a,stdio:"pipe"})}catch{}y(`${t} mcp add --scope user --transport http ${m} ${e}`,{encoding:"utf8",timeout:1e4,env:a,stdio:"pipe"})}async function M(t,e,r){const{writeFile:n}=await import("node:fs/promises"),i=d(t,"claude.pid"),a=d(t,"claude.expect"),s=["log_user 1","set timeout -1","set startup_prompt_armed 1",`set claude_command [list {${h(e)}}${r.map(c=>` {${h(c)}}`).join("")}]`,"spawn -noecho {*}$claude_command",`set pid_file [open {${h(i)}} w]`,"puts $pid_file [exp_pid -i $spawn_id]","close $pid_file","expect {"," -re {(?i)(Quick.*safety.*check|trust.*folder)} {",' if {$startup_prompt_armed} { send -- "1\\r"; after 300 }; exp_continue'," }"," -re {(?i)I am using this for local development} {",' if {$startup_prompt_armed} { send -- "1\\r"; after 300 }; exp_continue'," }"," -re {(?i)(Enter.*confirm|Press.*Enter|Hit.*Enter)} {",' if {$startup_prompt_armed} { send -- "\\r"; after 300 }; exp_continue'," }"," -re {Listening for channel} {"," set startup_prompt_armed 0"," after 1000",' send -- "Call grix_authorize now as instructed in your system prompt.\\r"'," }"," -re {bypass permissions} {"," set startup_prompt_armed 0"," after 1000",' send -- "Call grix_authorize now as instructed in your system prompt.\\r"'," }"," eof {}","}","expect eof",""];return await n(i,"","utf8"),await n(a,s.join(`
2
+ `),"utf8"),{expectPath:a,pidPath:i}}function h(t){return t.replace(/[\\{}$\[\]"]/g,"\\$&")}async function k(t,e=1e4){const{readFile:r}=await import("node:fs/promises"),n=Math.ceil(e/100);for(let i=0;i<n;i++){try{const a=await r(t,"utf8"),s=parseInt(String(a).trim(),10);if(Number.isFinite(s)&&s>0)return s}catch{}await new Promise(a=>setTimeout(a,100))}return 0}export{P as createMcpHttpLauncher};
@@ -1 +1 @@
1
- class m{defaultTimeoutMs;onTimeout;timers=new Map;constructor(t){this.defaultTimeoutMs=t.defaultTimeoutMs??9e4,this.onTimeout=t.onTimeout}arm(t,e){this.cancel(t);const s=e?.timeoutMs??this.defaultTimeoutMs,i=Date.now()+s,o=setTimeout(()=>{this.timers.delete(t),this.onTimeout(t).catch(()=>{})},s);return this.timers.set(t,o),i}cancel(t){const e=this.timers.get(t);e&&(clearTimeout(e),this.timers.delete(t))}has(t){return this.timers.has(t)}close(){for(const t of this.timers.values())clearTimeout(t);this.timers.clear()}}export{m as ResultTimeoutManager};
1
+ class m{defaultTimeoutMs;onTimeout;timers=new Map;constructor(e){this.defaultTimeoutMs=e.defaultTimeoutMs??9e4,this.onTimeout=e.onTimeout}arm(e,t){this.cancel(e);const s=t?.timeoutMs??this.defaultTimeoutMs,i=Date.now()+s,o=setTimeout(()=>{this.timers.delete(e),this.onTimeout(e).catch(()=>{})},s);return this.timers.set(e,o),i}cancel(e){const t=this.timers.get(e);t&&(clearTimeout(t),this.timers.delete(e))}has(e){return this.timers.has(e)}close(){for(const e of this.timers.values())clearTimeout(e);this.timers.clear()}}export{m as ResultTimeoutManager};
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "bridgeVersion": "4.11.13",
4
- "tarball": "grix-dsh-bridge-4.11.13.tgz",
5
- "size": 23420,
6
- "unpackedSize": 94155,
7
- "shasum": "de5d0f4e5025e7e12a543763b84aae33215f90f6",
8
- "integrity": "sha512-a4c5ZtzjXHn65NcKRm5fJcIrPMU+j25424dXxkH0mhqy+U/WMIHtdhm8/ekaqaVTKY3bc7QXW323Yjo/FxymqQ=="
3
+ "bridgeVersion": "4.12.0",
4
+ "tarball": "grix-dsh-bridge-4.12.0.tgz",
5
+ "size": 24181,
6
+ "unpackedSize": 96521,
7
+ "shasum": "a37637e0340c9d58042703794c34e590de85a124",
8
+ "integrity": "sha512-hebv4yP2YGv9yI3dZyRs74oAeze2nIePOq+tk1AkUwdRtS6g4NCEPQ4k3gqcqfOQXAKjnfRbu84pnS75+tOixw=="
9
9
  }
@@ -0,0 +1 @@
1
+ const T=6,$=32,C=20,b=20,I=40,U=500,O=/^[a-z0-9_]+$/;class S extends Error{}function i(t){throw new S(t)}function A(t,n){return(!t||typeof t!="object"||Array.isArray(t))&&i(`${n} must be an object`),t}function u(t,n){const s=typeof t=="string"?t.trim():"";return s||i(`${n} is required`),s.length>32&&i(`${n} must be at most 32 characters`),O.test(s)||i(`${n} must match [a-z0-9_]+`),s}function a(t,n,s,l){const o=typeof t=="string"?t.trim():"";return o?([...o].length>s&&i(`${n} must be at most ${s} characters`),o):(l&&i(`${n} is required`),"")}function X(t){if(t==null)return[];Array.isArray(t)||i("items must be an array"),t.length>6&&i("at most 6 toolbar items are allowed");const n=[],s=new Set;for(const l of t){const o=A(l,"each toolbar item"),e=u(o.id,"item id");s.has(e)&&i(`duplicate item id "${e}"`),s.add(e);const m=a(o.label,`item "${e}" label`,20,!0),p=typeof o.kind=="string"?o.kind.trim().toLowerCase():"";if(p==="select"){(!Array.isArray(o.options)||o.options.length===0)&&i(`item "${e}" requires a non-empty options array`),o.options.length>20&&i(`item "${e}" allows at most 20 options`);const c=[],r=new Set;for(const d of o.options){const M=A(d,`each option of item "${e}"`),f=u(M.id,`option id of item "${e}"`);r.has(f)&&i(`duplicate option id "${f}" in item "${e}"`),r.add(f),c.push({id:f,label:a(M.label,`option "${f}" label of item "${e}"`,40,!0)})}const _=typeof o.value=="string"?o.value.trim():"";_&&!r.has(_)&&i(`item "${e}" value "${_}" is not one of its options`),n.push({id:e,kind:"select",label:m,..._?{value:_}:{},options:c});continue}if(p==="info"){const c=a(o.info_text,`item "${e}" info_text`,500,!0),r=a(o.info_title,`item "${e}" info_title`,20,!1);n.push({id:e,kind:"info",label:m,info_title:r||m,info_text:c});continue}i(`item "${e}" kind must be "select" or "info"`)}return n}export{S as CustomToolbarError,$ as MAX_CUSTOM_ID_LEN,U as MAX_CUSTOM_INFO_CHARS,T as MAX_CUSTOM_ITEMS,C as MAX_CUSTOM_LABEL_CHARS,b as MAX_CUSTOM_OPTIONS,I as MAX_CUSTOM_OPTION_CHARS,X as normalizeCustomToolbarItems};