grix-connector 3.3.1 → 3.3.3

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
@@ -142,6 +142,47 @@ grix-connector start
142
142
 
143
143
  The daemon connects to Grix via WebSocket and starts routing chat messages to your agents.
144
144
 
145
+ > The daemon refuses to start when the config directory holds no valid agent config, so on a first-time setup the config file must be written **before** `start`.
146
+
147
+ ### Adding an agent to an existing setup
148
+
149
+ If `~/.grix/config/agents.json` already exists and other agents are running, **do not overwrite the file** — merge the new entry into it.
150
+
151
+ **1. Merge the entry.** Read the file as JSON and look through the `agents` array for an entry whose `agent_id` matches the one you are adding:
152
+
153
+ - found → replace that entry
154
+ - not found → append the new entry
155
+ - every other entry must be left exactly as it was
156
+
157
+ Edit it with a script (`node`, `python3`), not by hand — a truncated or re-indented file silently drops the agents you did not intend to touch. If the file does not exist yet, create it with a single-element `agents` array.
158
+
159
+ **2. Apply the change.** Check whether the daemon is already running:
160
+
161
+ ```bash
162
+ grix-connector status
163
+ ```
164
+
165
+ - not running (or it reports `daemon is not running`) → `grix-connector start`
166
+ - already running → `grix-connector reload`
167
+
168
+ `reload` hot-loads the new agent and leaves every already-running agent untouched — their live sessions are not interrupted. Do not use `restart` to add an agent: it reconnects everything and drops all in-flight conversations.
169
+
170
+ **3. Verify.** `grix-connector status` reports the daemon only — it does not list agents. To confirm the new agent is connected, query the local admin API (give it a few seconds after `start`/`reload`):
171
+
172
+ ```bash
173
+ curl -s http://127.0.0.1:19580/api/agents
174
+ ```
175
+
176
+ The entry for your agent should report `"alive": true`. `19580` is the default admin port; if it was overridden (`--admin-port` or `GRIX_ADMIN_PORT`), the port actually in use is written to `~/.grix/data/admin-port`.
177
+
178
+ If it never connects, read the newest log under `~/.grix/log/`. In practice it is almost always one of three things:
179
+
180
+ - the CLI for that `client_type` is not on `PATH`
181
+ - the CLI is installed but not logged in
182
+ - the `api_key` was truncated when it was copied
183
+
184
+ **Credentials.** `api_key` is a one-time secret: write it into `~/.grix/config/agents.json` and nowhere else. Do not echo it into logs or commit it to a repository.
185
+
145
186
  ### Commands
146
187
 
147
188
  ```bash
@@ -224,11 +265,34 @@ Or manually add to your OpenClaw project:
224
265
  npm install grix-connector
225
266
  ```
226
267
 
268
+ ### Connecting an agent (plugin mode)
269
+
270
+ Plugin mode does **not** use `~/.grix/config/agents.json`. The credentials live in OpenClaw's own config (`openclaw.json`) under `channels.grix`, and the field names are camelCase — `wsUrl` / `agentId` / `apiKey` — not the snake_case names used by the daemon.
271
+
272
+ Write them with `openclaw config set`. **Do not hand-edit `openclaw.json`.**
273
+
274
+ ```bash
275
+ openclaw config set channels.grix.accounts.<account-id> \
276
+ '{"name":"my-agent","enabled":true,"wsUrl":"wss://grix.dhf.pub/v1/agent-api/ws","agentId":"your-agent-id","apiKey":"your-grix-api-key"}' \
277
+ --strict-json
278
+ ```
279
+
280
+ `<account-id>` is a name you choose (lowercase letters, digits, hyphens). Use the `ws.grix.im` domain instead for the global region. If `channels.grix.enabled` was explicitly turned off before, also run `openclaw config set channels.grix.enabled true --strict-json`.
281
+
282
+ Then check it:
283
+
284
+ ```bash
285
+ openclaw config validate
286
+ openclaw grix doctor # the account should report configured: true, enabled: true
287
+ ```
288
+
289
+ Finally send a message to the agent from Grix and confirm the reply really comes from it. Only if the config validates but messages do not route should you run `openclaw gateway restart` — once — and then re-send the message.
290
+
227
291
  ### Plugin Features
228
292
 
229
293
  - **Channel**: Grix chat transport — routes messages between OpenClaw and your Grix deployment
230
294
  - **Tools**: `grix_query`, `grix_group`, `grix_admin`, `grix_egg`, `grix_register`, `grix_update`, `grix_message_send`, `grix_message_unsend`, `openclaw_memory_setup`
231
- - **CLI**: `openclaw grix` — agent management and admin commands
295
+ - **CLI**: `openclaw grix doctor` — local diagnostics (the only subcommand; configuration goes through `openclaw config set`)
232
296
  - **Skills**: 9 bundled skills for admin, group, query, registration, update, messaging, memory setup, and egg orchestration
233
297
 
234
298
  ### Requirements
@@ -236,6 +300,8 @@ npm install grix-connector
236
300
  - OpenClaw >= 2026.4.8
237
301
  - A Grix account with agent ID and API key
238
302
 
303
+ Plugin mode and the standalone daemon can coexist on one machine — they use separate configs and separate processes. Do not, however, bring the **same** `agent_id` online in both at once; give each mode its own agent.
304
+
239
305
  ## License
240
306
 
241
307
  MIT
@@ -1,5 +1,5 @@
1
- import{fileURLToPath as A}from"node:url";import f from"node:path";import{homedir as C}from"node:os";import{promises as v}from"node:fs";import{EventEmitter as I}from"node:events";import{spawn as R}from"node:child_process";import{AgentProcess as T}from"../../agent/process.js";import{hasChildProcesses as w}from"../../core/runtime/spawn.js";import{applyNativeProviderConfig as k}from"../shared/native-provider-config.js";import{syncDefaultSkillsToDir as _}from"../../default-skills/index.js";import{AcpClient as $,AcpAuthRequiredError as P,isAuthRequiredError as E}from"../../protocol/acp-client.js";import{AgentEventType as m}from"../../types/events.js";import{InternalApiServer as M}from"../../core/mcp/internal-api-server.js";import{EventResultsStore as x}from"../../core/persistence/event-results-store.js";import{QuotedMessageStream as q}from"../../core/util/quoted-message-stream.js";import{SafeMarkdownStreamSegmenter as D}from"../../core/text-segmentation/index.js";import{extractAcpTurnInput as N}from"../../core/protocol/payload-parser.js";import{injectMessageMetadata as j}from"../../core/protocol/message-metadata.js";import{log as r}from"../../core/log/index.js";import{scanSkills as S}from"../claude/skill-scanner.js";import{checkKiroSessionActivity as B}from"./session-activity.js";import{resolveCliPath as U,getCliVersion as O}from"../../core/util/cli-probe.js";const b=f.dirname(A(import.meta.url)),F=[{modelId:"deepseek-v4-pro",name:"DeepSeek V4 Pro"},{modelId:"deepseek-v4-flash",name:"DeepSeek V4 Flash"}],L=200,K=60*1e3,H=600*1e3,y=80,W=["[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 z(h){return!(!(h instanceof Error)||h.code!==-32603||E(h))}function g(h){if(!(h instanceof Error))return String(h);let e=h.message;const i=h.data;return i&&(e+=` data=${JSON.stringify(i)}`),e}function V(h){return h.replace(/\u001b\[[0-9;?]*[ -/]*[@-~]/g,"")}class ve extends I{type="acp";config;callbacks;agentProcess=null;acpClient=null;internalApi=null;activeRun=null;compacting=!1;compactingTimer=null;compactionDoneResolver=null;pendingAutoCompact=!1;eventResults=null;pendingApprovals=new Map;clientMsgSeq=0;stopped=!1;acpAuthMethod;acpInitialMode;acpInitialModel;acpMcpTools;rawTransport;approvalMode;autoInjectArgs;bindingStore;sessionBindings=new Map;deferredEvents=new Map;currentAibotSessionId;bridgeLog;sessionConnected=!1;rawEventSeq=0;recoveryContextBySessionId=new Map;quoteProtocolTaughtSessions=new Set;identityInjectedSessions=new Set;constructor(e,i,t){if(super(),this.config=e,this.callbacks=i,this.bridgeLog=t?.bridgeLog??null,this.acpAuthMethod=t?.acpAuthMethod,this.acpInitialMode=t?.acpInitialMode,this.acpInitialModel=t?.acpInitialModel,this.acpMcpTools=t?.acpMcpTools??!0,this.rawTransport=t?.rawTransport??!1,this.approvalMode=t?.approvalMode??"default",this.autoInjectArgs=t?.autoInjectArgs,this.bindingStore=t?.bindingStore??null,this.currentAibotSessionId=t?.aibotSessionId?String(t.aibotSessionId).trim():void 0,t?.eventResultsPath&&(this.eventResults=new x(t.eventResultsPath)),this.bindingStore&&this.currentAibotSessionId){const s=this.bindingStore.get(this.currentAibotSessionId);s?.cwd&&this.sessionBindings.set(this.currentAibotSessionId,s.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.activeRun.flushTimer&&(clearTimeout(this.activeRun.flushTimer),this.activeRun.flushTimer=null),this.activeRun.idleTimer&&(clearTimeout(this.activeRun.idleTimer),this.activeRun.idleTimer=null),this.activeRun=null),this.acpClient&&(this.acpClient.clearSettleTimer(),this.acpClient.removeAllListeners(),this.acpClient=null),this.agentProcess&&(await this.agentProcess.close(),this.agentProcess=null),this.internalApi&&(await this.internalApi.stop(),this.internalApi=null)}isAlive(){return this.agentProcess?.alive??!1}async createSession(e){return this.acpClient?.sessionId??""}async resumeSession(e,i){}async destroySession(e){this.quoteProtocolTaughtSessions.delete(e),this.identityInjectedSessions.delete(e)}onAgentProfileChanged(){this.identityInjectedSessions.size>0&&(r.info("acp-adapter",`agent profile changed; clearing ${this.identityInjectedSessions.size} identity-injected session(s)`),this.identityInjectedSessions.clear())}sendPrompt(e){const i=new Q(e.adapterSessionId);return this.acpClient?.isAlive&&this.acpClient.send(e.text).catch(t=>{i.emitError(t instanceof Error?t:new Error(String(t)))}),i}async cancel(e){this.activeRun&&this.acpClient&&(await this.acpClient.cancel(),this.flushStream(),this.finishRun("canceled","stopped by user"))}setPermissionHandler(e){}async ping(e){return this.acpClient?.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.activeRun.flushTimer&&(clearTimeout(this.activeRun.flushTimer),this.activeRun.flushTimer=null),this.activeRun.idleTimer&&(clearTimeout(this.activeRun.idleTimer),this.activeRun.idleTimer=null),this.activeRun=null)}getMcpConfig(){if(!this.internalApi)return null;this.internalApi.setMcpBridgeUpHandler(i=>this.onMcpBridgeUp(i));const e=f.resolve(b,"../../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?w(e,[e]):!1}async probe(e){const i=this.config.command||"gemini",t=await U(i),s=t!==null;let n=null,a;if(s){const l=await O(i);n=l.version,l.error&&(a=l.error)}else a={code:"cli_not_found",message:`command not found: ${i}`};const o=this.acpClient?.model??null,c=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:i,installed:s,path:t,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:c.alive,busy:c.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,i,t){return this.acpClient?.supportsCommandsExecute?this.execCommandViaAcp(e,i):this.execCommandFallback(e,i)}async execCommandViaAcp(e,i){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 t=await this.acpClient.executeCommand(e,i||void 0);return this.finishCompaction("acp-commands-execute"),{status:t.status==="failed"?"failed":"ok",message:t.message??"Compacted",data:t.data}}catch(t){return this.finishCompaction("error"),t?.code===-32601?this.execCommandFallback(e,i):{status:"failed",message:`compact failed: ${t instanceof Error?t.message:t}`}}}try{const t=await this.acpClient.executeCommand(e,i||void 0);return{status:t.status??"ok",message:t.message,data:t.options?{command:e,options:t.options}:t.data}}catch(t){return t?.code===-32601?this.execCommandFallback(e,i):{status:"failed",message:`Command failed: ${t instanceof Error?t.message:t}`}}}async execCommandFallback(e,i){try{switch(e){case"model":{const t=i.trim();if(t)return await this.setModel(t)?{status:"ok",message:`Model set to ${t}`}:{status:"failed",message:`Failed to set model: ${t}`};const s=this.buildToolbarContext("model_list");return{status:"ok",message:`Current: ${s.currentModelId||"unknown"}`,data:s}}case"mode":{const t=i.trim();if(t)return await this.setMode(t)?{status:"ok",message:`Mode set to ${t}`}:{status:"failed",message:`Failed to set mode: ${t}`};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 t=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 t,{status:"ok",message:"Compacted"}}case"status":{const t=this.getStatus(),s=this.acpClient?.sessionOptions;return{status:"ok",message:`Alive: ${t.alive}, Busy: ${t.busy}, Model: ${s?.currentModelId??"unknown"}, Mode: ${s?.currentModeId??"unknown"}`,data:{alive:t.alive,busy:t.busy,sessions:t.sessions,model:s?.currentModelId??"",mode:s?.currentModeId??""}}}case"skills":{const t=S({mode:this.config.command==="kiro-cli"?"kiro":"gemini",projectDir:process.cwd()}),s=t.map(n=>`- ${n.name}${n.trigger?` (${n.trigger})`:""} [${n.source}]: ${n.description}`);return{status:"ok",message:s.length>0?s.join(`
2
- `):"No skills found",data:t}}default:return{status:"unsupported",message:`Unknown command: ${e}`}}}catch(t){return{status:"failed",message:t instanceof Error?t.message:String(t)}}}get acpSessionOptions(){return this.acpClient?.sessionOptions??null}get isReasonix(){return(this.config.command??"").includes("reasonix")}buildToolbarContext(e,i){const t=this.acpClient?.sessionOptions,s=t?.currentModeId??"";let n=t?.currentModelId??"";const a=t?.modes.map(u=>({id:u.id,name:u.name}))??[],o=t?.models&&t.models.length>0?t.models:this.isReasonix?F:[],c=o.map(u=>({modelId:u.modelId,name:u.name})),d=o.map(u=>({id:u.modelId,displayName:u.name})),l=t?.modes.map(u=>({id:u.id,displayName:u.name}))??[];d.length>0&&!d.some(u=>u.id===n)&&(n=d[0].id);const p={outcome:e,...i?{cwd:i}:{},model_id:n,mode_id:s,currentModelId:n,currentModeId:s,available_models:d,available_modes:l,availableModels:d,availableModes:l,models:c,modes:a};return r.info("acp-adapter",`[toolbar] buildToolbarContext outcome=${e} model_id="${n}" available_models=${JSON.stringify(d.map(u=>u.id))} currentModelId=${n}`),p}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,i){const t=this.pendingApprovals.get(e);return t?(this.pendingApprovals.delete(e),this.acpClient&&this.acpClient.respondPermission(t,{behavior:i}).catch(s=>{r.error("acp-adapter",`Failed to respond to permission: ${s}`)}),this.activeRun&&this.resetIdleTimer(this.activeRun),!0):!1}respondToPermission(e,i){this.acpClient&&this.acpClient.respondPermission(e,i).catch(t=>{r.error("acp-adapter",`Failed to respond to permission: ${t}`)}),this.activeRun&&this.resetIdleTimer(this.activeRun)}async handleLocalAction(e){const i=e.action_type??"",t=e.params??{};if(i==="exec_approve"||i==="exec_reject"||i==="permission_approve"||i==="permission_reject"){const s=String(t.tool_call_id??t.approval_command_id??t.approval_id??t.exec_context_id??""),n=i==="exec_approve"||i==="permission_approve",a=String(t.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:""}}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,i){const t=this.sessionBindings.get(e);if(t)try{return await v.stat(t),!1}catch{r.info("acp-adapter",`Stale binding for session ${e}: ${t} no longer exists, allowing rebind to ${i}`),this.sessionBindings.delete(e)}return this.sessionBindings.set(e,i),this.bindingStore&&this.bindingStore.set(e,i),this.maybeRefreshKiroSkills(i),!this.sessionConnected&&this.agentProcess?.alive?this.connectSession(i).then(()=>!0).catch(s=>(r.error("acp-adapter",`Failed to create session on bind: ${g(s)}`),this.callbacks.sendUpdateBindingCard(e,"failed",i),!0)):(this.acpClient?.isAlive&&(this.bindingStore&&this.acpClient.sessionId&&this.bindingStore.setAcpSessionId(e,this.acpClient.sessionId),this.callbacks.sendUpdateBindingCard(e,"connected",i,this.buildToolbarContext("binding_ready",i))),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 i=String(e??"").trim()||void 0;let t=[];try{t=S({mode:"kiro",projectDir:i})}catch(s){r.warn("acp-adapter",`Kiro skills scan failed: ${s instanceof Error?s.message:String(s)}`);return}try{this.callbacks.onSkillsUpdate(t)}catch(s){r.warn("acp-adapter",`onSkillsUpdate failed: ${s instanceof Error?s.message:String(s)}`)}}replayDeferredEvents(e){const i=this.deferredEvents.get(e);if(!i||i.length===0)return;if(this.activeRun){r.info("acp-adapter",`Cannot replay deferred events for session ${e}: agent busy`);return}const t=i.shift();t&&(i.length===0?(this.deferredEvents.delete(e),this.cancelDeferredTimer(e)):this.deferredEvents.set(e,i),r.info("acp-adapter",`Replaying deferred event ${t.event.event_id} for session ${e} (remaining: ${i.length})`),this.startRun(t.event,!1))}finishCompaction(e){if(!this.compacting)return;this.compacting=!1,this.compactingTimer&&(clearTimeout(this.compactingTimer),this.compactingTimer=null),r.info("acp-adapter",`Compaction finished (${e}); replaying deferred events`),this.replayNextDeferredEvent();const i=this.compactionDoneResolver;this.compactionDoneResolver=null,i?.()}replayNextDeferredEvent(){if(this.activeRun||this.compacting)return;const e=this.deferredEvents.keys().next().value;e&&this.replayDeferredEvents(e)}announceDeferredComposing(e){}deliverInboundEvent(e){if(this.eventResults?.has(e.session_id,e.event_id)){const i=this.eventResults.get(e.session_id,e.event_id);r.info("acp-adapter",`Deduplicating event ${e.event_id} (cached: ${i.status})`),this.callbacks.sendEventResult(e.event_id,i.status,i.msg);return}if(this.compacting){r.info("acp-adapter",`Event ${e.event_id} deferred: compaction in progress`),this.deferEvent(e);return}if(this.activeRun){r.info("acp-adapter",`Event ${e.event_id} rejected: busy`),this.callbacks.sendEventResult(e.event_id,"failed","agent busy");return}if(!this.agentProcess?.alive){this.callbacks.sendEventResult(e.event_id,"failed","agent not alive");return}e.session_id&&e.session_id!==this.currentAibotSessionId&&(this.currentAibotSessionId=e.session_id),this.startRun(e,!1)}deliverStopEvent(e,i){this.acpClient?(this.acpClient.cancel().catch(()=>{}),this.flushStream()):this.flushStream(),this.activeRun?.eventId===e&&this.finishRun("canceled","stopped by user")}deferEvent(e){const i=this.deferredEvents.get(e.session_id)??[];i.some(t=>t.event.event_id===e.event_id)||(i.push({event:e,queuedAt:Date.now()}),this.deferredEvents.set(e.session_id,i),r.info("acp-adapter",`Deferred event ${e.event_id} for session ${e.session_id} (queue: ${i.length})`),this.scheduleDeferredTimeout(e.session_id))}deferredTimers=new Map;rejectDeferredEvents(e){for(const[i,t]of this.deferredEvents)for(const{event:s}of t)r.info("acp-adapter",`Rejecting deferred event ${s.event_id}: ${e}`),this.callbacks.sendEventResult(s.event_id,"failed",e);this.deferredEvents.clear(),this.cancelAllDeferredTimers()}cancelDeferredTimer(e){const i=this.deferredTimers.get(e);i&&(clearTimeout(i),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 i=3e4,t=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 ${i/1e3}s (no ACP client)`),this.deferredEvents.delete(e);for(const{event:n}of s)this.callbacks.sendEventResult(n.event_id,"failed","agent initialization timed out")}},i);this.deferredTimers.set(e,t)}startRun(e,i){if(!this.acpClient?.isAlive){this.deferEvent(e);return}const t=`acp_${++this.clientMsgSeq}_${Date.now()}`;this.activeRun={eventId:e.event_id,sessionId:e.session_id,threadId:e.thread_id,clientMsgIdBase:t,currentClientMsgId:t,currentSegmentIndex:0,chunkSeq:0,buffer:"",quotedStream:new q,markdownSegmenter:new D,quotedMessageId:void 0,flushTimer:null,idleTimer:null,awaitingToolResult:!1,responded:!1,silent:i,lastProgressAt:Date.now(),lastIdleCheckAt:Date.now(),idleNoProgressCount:0};const s=this.activeRun,n=N(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=j(n.prompt,{messageId:e.msg_id,quotedMessageId:e.quoted_message_id}),o=this.injectQuoteProtocolOnce(s.sessionId,a),c=this.injectIdentityOnce(s.sessionId,o),d=this.injectRecoveryContext(s.sessionId,c);this.sendPromptWithRetry(s,d)}injectQuoteProtocolOnce(e,i){return!e||this.quoteProtocolTaughtSessions.has(e)?i:(this.quoteProtocolTaughtSessions.add(e),`${W}
1
+ import{fileURLToPath as A}from"node:url";import f from"node:path";import{homedir as C}from"node:os";import{promises as v}from"node:fs";import{EventEmitter as S}from"node:events";import{spawn as R}from"node:child_process";import{AgentProcess as T}from"../../agent/process.js";import{hasChildProcesses as w}from"../../core/runtime/spawn.js";import{applyNativeProviderConfig as k}from"../shared/native-provider-config.js";import{syncDefaultSkillsToDir as _}from"../../default-skills/index.js";import{AcpClient as $,AcpAuthRequiredError as P,isAuthRequiredError as E}from"../../protocol/acp-client.js";import{AgentEventType as m}from"../../types/events.js";import{InternalApiServer as M}from"../../core/mcp/internal-api-server.js";import{EventResultsStore as x}from"../../core/persistence/event-results-store.js";import{QuotedMessageStream as q}from"../../core/util/quoted-message-stream.js";import{SafeMarkdownStreamSegmenter as D}from"../../core/text-segmentation/index.js";import{extractAcpTurnInput as N}from"../../core/protocol/payload-parser.js";import{injectMessageMetadata as j}from"../../core/protocol/message-metadata.js";import{log as r}from"../../core/log/index.js";import{scanSkills as b}from"../claude/skill-scanner.js";import{checkKiroSessionActivity as B}from"./session-activity.js";import{resolveCliPath as U,getCliVersion as O}from"../../core/util/cli-probe.js";const I=f.dirname(A(import.meta.url)),F=[{modelId:"deepseek-v4-pro",name:"DeepSeek V4 Pro"},{modelId:"deepseek-v4-flash",name:"DeepSeek V4 Flash"}],L=200,K=60*1e3,H=600*1e3,y=80,W=["[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 z(h){return!(!(h instanceof Error)||h.code!==-32603||E(h))}function g(h){if(!(h instanceof Error))return String(h);let e=h.message;const i=h.data;return i&&(e+=` data=${JSON.stringify(i)}`),e}function V(h){return h.replace(/\u001b\[[0-9;?]*[ -/]*[@-~]/g,"")}class ve extends S{type="acp";config;callbacks;agentProcess=null;acpClient=null;internalApi=null;activeRun=null;compacting=!1;compactingTimer=null;compactionDoneResolver=null;pendingAutoCompact=!1;eventResults=null;pendingApprovals=new Map;clientMsgSeq=0;stopped=!1;acpAuthMethod;acpInitialMode;acpInitialModel;acpMcpTools;rawTransport;approvalMode;autoInjectArgs;bindingStore;sessionBindings=new Map;deferredEvents=new Map;currentAibotSessionId;bridgeLog;sessionConnected=!1;rawEventSeq=0;recoveryContextBySessionId=new Map;quoteProtocolTaughtSessions=new Set;identityInjectedSessions=new Set;constructor(e,i,t){if(super(),this.config=e,this.callbacks=i,this.bridgeLog=t?.bridgeLog??null,this.acpAuthMethod=t?.acpAuthMethod,this.acpInitialMode=t?.acpInitialMode,this.acpInitialModel=t?.acpInitialModel,this.acpMcpTools=t?.acpMcpTools??!0,this.rawTransport=t?.rawTransport??!1,this.approvalMode=t?.approvalMode??"default",this.autoInjectArgs=t?.autoInjectArgs,this.bindingStore=t?.bindingStore??null,this.currentAibotSessionId=t?.aibotSessionId?String(t.aibotSessionId).trim():void 0,t?.eventResultsPath&&(this.eventResults=new x(t.eventResultsPath)),this.bindingStore&&this.currentAibotSessionId){const s=this.bindingStore.get(this.currentAibotSessionId);s?.cwd&&this.sessionBindings.set(this.currentAibotSessionId,s.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.activeRun.flushTimer&&(clearTimeout(this.activeRun.flushTimer),this.activeRun.flushTimer=null),this.activeRun.idleTimer&&(clearTimeout(this.activeRun.idleTimer),this.activeRun.idleTimer=null),this.activeRun=null),this.acpClient&&(this.acpClient.clearSettleTimer(),this.acpClient.removeAllListeners(),this.acpClient=null),this.agentProcess&&(await this.agentProcess.close(),this.agentProcess=null),this.internalApi&&(await this.internalApi.stop(),this.internalApi=null)}isAlive(){return this.agentProcess?.alive??!1}async createSession(e){return this.acpClient?.sessionId??""}async resumeSession(e,i){}async destroySession(e){this.quoteProtocolTaughtSessions.delete(e),this.identityInjectedSessions.delete(e)}onAgentProfileChanged(){this.identityInjectedSessions.size>0&&(r.info("acp-adapter",`agent profile changed; clearing ${this.identityInjectedSessions.size} identity-injected session(s)`),this.identityInjectedSessions.clear())}sendPrompt(e){const i=new Q(e.adapterSessionId);return this.acpClient?.isAlive&&this.acpClient.send(e.text).catch(t=>{i.emitError(t instanceof Error?t:new Error(String(t)))}),i}async cancel(e){this.activeRun&&this.acpClient&&(await this.acpClient.cancel(),this.flushStream(),this.finishRun("canceled","stopped by user"))}setPermissionHandler(e){}async ping(e){return this.acpClient?.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.activeRun.flushTimer&&(clearTimeout(this.activeRun.flushTimer),this.activeRun.flushTimer=null),this.activeRun.idleTimer&&(clearTimeout(this.activeRun.idleTimer),this.activeRun.idleTimer=null),this.activeRun=null)}getMcpConfig(){if(!this.internalApi)return null;this.internalApi.setMcpBridgeUpHandler(i=>this.onMcpBridgeUp(i));const e=f.resolve(I,"../../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?w(e,[e]):!1}async probe(e){const i=this.config.command||"gemini",t=await U(i),s=t!==null;let n=null,a;if(s){const d=await O(i);n=d.version,d.error&&(a=d.error)}else a={code:"cli_not_found",message:`command not found: ${i}`};const o=this.acpClient?.model??null,c=this.getStatus();let l;return e?.conversation?this.acpClient?.isAlive?l={attempted:!0,ok:!0,latency_ms:null}:l={attempted:!0,ok:!1,latency_ms:null,error:{code:"unsupported",message:"ACP session not connected, cannot probe conversation"}}:l={attempted:!1,ok:!1,latency_ms:null},{cli:{command:i,installed:s,path:t,version:n,...a?{error:a}:{}},conversation:l,config:{model:o,base_url:null,source:{model:o?"runtime":"unknown",base_url:"unknown"}},process:{started:!!this.agentProcess,alive:c.alive,busy:c.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,i,t){return this.acpClient?.supportsCommandsExecute?this.execCommandViaAcp(e,i):this.execCommandFallback(e,i)}async execCommandViaAcp(e,i){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 t=await this.acpClient.executeCommand(e,i||void 0);return this.finishCompaction("acp-commands-execute"),{status:t.status==="failed"?"failed":"ok",message:t.message??"Compacted",data:t.data}}catch(t){return this.finishCompaction("error"),t?.code===-32601?this.execCommandFallback(e,i):{status:"failed",message:`compact failed: ${t instanceof Error?t.message:t}`}}}try{const t=await this.acpClient.executeCommand(e,i||void 0);return{status:t.status??"ok",message:t.message,data:t.options?{command:e,options:t.options}:t.data}}catch(t){return t?.code===-32601?this.execCommandFallback(e,i):{status:"failed",message:`Command failed: ${t instanceof Error?t.message:t}`}}}async execCommandFallback(e,i){try{switch(e){case"model":{const t=i.trim();if(t)return await this.setModel(t)?{status:"ok",message:`Model set to ${t}`}:{status:"failed",message:`Failed to set model: ${t}`};const s=this.buildToolbarContext("model_list");return{status:"ok",message:`Current: ${s.currentModelId||"unknown"}`,data:s}}case"mode":{const t=i.trim();if(t)return await this.setMode(t)?{status:"ok",message:`Mode set to ${t}`}:{status:"failed",message:`Failed to set mode: ${t}`};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 t=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 t,{status:"ok",message:"Compacted"}}case"status":{const t=this.getStatus(),s=this.acpClient?.sessionOptions;return{status:"ok",message:`Alive: ${t.alive}, Busy: ${t.busy}, Model: ${s?.currentModelId??"unknown"}, Mode: ${s?.currentModeId??"unknown"}`,data:{alive:t.alive,busy:t.busy,sessions:t.sessions,model:s?.currentModelId??"",mode:s?.currentModeId??""}}}case"skills":{const t=b({mode:this.config.command==="kiro-cli"?"kiro":"gemini",projectDir:process.cwd()}),s=t.map(n=>`- ${n.name}${n.trigger?` (${n.trigger})`:""} [${n.source}]: ${n.description}`);return{status:"ok",message:s.length>0?s.join(`
2
+ `):"No skills found",data:t}}default:return{status:"unsupported",message:`Unknown command: ${e}`}}}catch(t){return{status:"failed",message:t instanceof Error?t.message:String(t)}}}get acpSessionOptions(){return this.acpClient?.sessionOptions??null}get isReasonix(){return(this.config.command??"").includes("reasonix")}buildToolbarContext(e,i){const t=this.acpClient?.sessionOptions,s=t?.currentModeId??"";let n=t?.currentModelId??"";const a=t?.modes.map(u=>({id:u.id,name:u.name}))??[],o=t?.models&&t.models.length>0?t.models:this.isReasonix?F:[],c=o.map(u=>({modelId:u.modelId,name:u.name})),l=o.map(u=>({id:u.modelId,displayName:u.name})),d=t?.modes.map(u=>({id:u.id,displayName:u.name}))??[];l.length>0&&!l.some(u=>u.id===n)&&(n=l[0].id);const p={outcome:e,...i?{cwd:i}:{},model_id:n,mode_id:s,currentModelId:n,currentModeId:s,available_models:l,available_modes:d,availableModels:l,availableModes:d,models:c,modes:a};return r.info("acp-adapter",`[toolbar] buildToolbarContext outcome=${e} model_id="${n}" available_models=${JSON.stringify(l.map(u=>u.id))} currentModelId=${n}`),p}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,i){const t=this.pendingApprovals.get(e);return t?(this.pendingApprovals.delete(e),this.acpClient&&this.acpClient.respondPermission(t,{behavior:i}).catch(s=>{r.error("acp-adapter",`Failed to respond to permission: ${s}`)}),this.activeRun&&this.resetIdleTimer(this.activeRun),!0):!1}respondToPermission(e,i){this.acpClient&&this.acpClient.respondPermission(e,i).catch(t=>{r.error("acp-adapter",`Failed to respond to permission: ${t}`)}),this.activeRun&&this.resetIdleTimer(this.activeRun)}async handleLocalAction(e){const i=e.action_type??"",t=e.params??{};if(i==="exec_approve"||i==="exec_reject"||i==="permission_approve"||i==="permission_reject"){const s=String(t.tool_call_id??t.approval_command_id??t.approval_id??t.exec_context_id??""),n=i==="exec_approve"||i==="permission_approve",a=String(t.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:""}}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,i){const t=this.sessionBindings.get(e);if(t)try{return await v.stat(t),!1}catch{r.info("acp-adapter",`Stale binding for session ${e}: ${t} no longer exists, allowing rebind to ${i}`),this.sessionBindings.delete(e)}return this.sessionBindings.set(e,i),this.bindingStore&&this.bindingStore.set(e,i),this.maybeRefreshKiroSkills(i),!this.sessionConnected&&this.agentProcess?.alive?this.connectSession(i).then(()=>!0).catch(s=>(r.error("acp-adapter",`Failed to create session on bind: ${g(s)}`),this.callbacks.sendUpdateBindingCard(e,"failed",i),!0)):(this.acpClient?.isAlive&&(this.bindingStore&&this.acpClient.sessionId&&this.bindingStore.setAcpSessionId(e,this.acpClient.sessionId),this.callbacks.sendUpdateBindingCard(e,"connected",i,this.buildToolbarContext("binding_ready",i))),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 i=String(e??"").trim()||void 0;let t=[];try{t=b({mode:"kiro",projectDir:i})}catch(s){r.warn("acp-adapter",`Kiro skills scan failed: ${s instanceof Error?s.message:String(s)}`);return}try{this.callbacks.onSkillsUpdate(t)}catch(s){r.warn("acp-adapter",`onSkillsUpdate failed: ${s instanceof Error?s.message:String(s)}`)}}replayDeferredEvents(e){const i=this.deferredEvents.get(e);if(!i||i.length===0)return;if(this.activeRun){r.info("acp-adapter",`Cannot replay deferred events for session ${e}: agent busy`);return}const t=i.shift();t&&(i.length===0?(this.deferredEvents.delete(e),this.cancelDeferredTimer(e)):this.deferredEvents.set(e,i),r.info("acp-adapter",`Replaying deferred event ${t.event.event_id} for session ${e} (remaining: ${i.length})`),this.startRun(t.event,!1))}finishCompaction(e){if(!this.compacting)return;this.compacting=!1,this.compactingTimer&&(clearTimeout(this.compactingTimer),this.compactingTimer=null),r.info("acp-adapter",`Compaction finished (${e}); replaying deferred events`),this.replayNextDeferredEvent();const i=this.compactionDoneResolver;this.compactionDoneResolver=null,i?.()}replayNextDeferredEvent(){if(this.activeRun||this.compacting)return;const e=this.deferredEvents.keys().next().value;e&&this.replayDeferredEvents(e)}announceDeferredComposing(e){}deliverInboundEvent(e){if(this.eventResults?.has(e.session_id,e.event_id)){const i=this.eventResults.get(e.session_id,e.event_id);r.info("acp-adapter",`Deduplicating event ${e.event_id} (cached: ${i.status})`),this.callbacks.sendEventResult(e.event_id,i.status,i.msg);return}if(this.compacting){r.info("acp-adapter",`Event ${e.event_id} deferred: compaction in progress`),this.deferEvent(e);return}if(this.activeRun){r.info("acp-adapter",`Event ${e.event_id} rejected: busy`),this.callbacks.sendEventResult(e.event_id,"failed","agent busy");return}if(!this.agentProcess?.alive){this.callbacks.sendEventResult(e.event_id,"failed","agent not alive");return}e.session_id&&e.session_id!==this.currentAibotSessionId&&(this.currentAibotSessionId=e.session_id),this.startRun(e,!1)}deliverStopEvent(e,i){this.acpClient?(this.acpClient.cancel().catch(()=>{}),this.flushStream()):this.flushStream(),this.activeRun?.eventId===e&&this.finishRun("canceled","stopped by user")}deferEvent(e){const i=this.deferredEvents.get(e.session_id)??[];i.some(t=>t.event.event_id===e.event_id)||(i.push({event:e,queuedAt:Date.now()}),this.deferredEvents.set(e.session_id,i),r.info("acp-adapter",`Deferred event ${e.event_id} for session ${e.session_id} (queue: ${i.length})`),this.scheduleDeferredTimeout(e.session_id))}deferredTimers=new Map;rejectDeferredEvents(e){for(const[i,t]of this.deferredEvents)for(const{event:s}of t)r.info("acp-adapter",`Rejecting deferred event ${s.event_id}: ${e}`),this.callbacks.sendEventResult(s.event_id,"failed",e);this.deferredEvents.clear(),this.cancelAllDeferredTimers()}cancelDeferredTimer(e){const i=this.deferredTimers.get(e);i&&(clearTimeout(i),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 i=3e4,t=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 ${i/1e3}s (no ACP client)`),this.deferredEvents.delete(e);for(const{event:n}of s)this.callbacks.sendEventResult(n.event_id,"failed","agent initialization timed out")}},i);this.deferredTimers.set(e,t)}startRun(e,i){if(!this.acpClient?.isAlive){this.deferEvent(e);return}const t=`acp_${++this.clientMsgSeq}_${Date.now()}`;this.activeRun={eventId:e.event_id,sessionId:e.session_id,threadId:e.thread_id,clientMsgIdBase:t,currentClientMsgId:t,currentSegmentIndex:0,chunkSeq:0,buffer:"",quotedStream:new q,markdownSegmenter:new D,quotedMessageId:void 0,flushTimer:null,idleTimer:null,awaitingToolResult:!1,responded:!1,silent:i,lastProgressAt:Date.now(),lastIdleCheckAt:Date.now(),idleNoProgressCount:0};const s=this.activeRun,n=N(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=j(n.prompt,{messageId:e.msg_id,quotedMessageId:e.quoted_message_id}),o=this.injectQuoteProtocolOnce(s.sessionId,a),c=this.injectIdentityOnce(s.sessionId,o),l=this.injectRecoveryContext(s.sessionId,c);this.sendPromptWithRetry(s,l)}injectQuoteProtocolOnce(e,i){return!e||this.quoteProtocolTaughtSessions.has(e)?i:(this.quoteProtocolTaughtSessions.add(e),`${W}
3
3
 
4
4
  ${i}`)}injectIdentityOnce(e,i){if(!e||this.identityInjectedSessions.has(e))return i;const t=this.callbacks.getAgentProfile?.(),s=t?.agentName?.trim()??"",n=t?.introduction?.trim()??"";if(!s&&!n)return i;this.identityInjectedSessions.add(e);const a=["[system-identity]"];return s&&a.push(`Your name is "${s}".`),n&&a.push(`Your self-introduction: ${n}`),a.push("Treat this as an out-of-band instruction; do not echo or repeat it in your replies."),a.push("[/system-identity]"),r.info("acp-adapter",`Injected agent identity for session=${e} name="${s}"`),`${a.join(`
5
5
  `)}
@@ -7,14 +7,14 @@ ${i}`)}injectIdentityOnce(e,i){if(!e||this.identityInjectedSessions.has(e))retur
7
7
  ${i}`}injectRecoveryContext(e,i){const t=this.recoveryContextBySessionId.get(e);return t?(this.recoveryContextBySessionId.delete(e),r.info("acp-adapter",`Injecting recovery context for session ${e} (chars=${t.length})`),`${t}
8
8
 
9
9
  [\u5F53\u524D\u7528\u6237\u6D88\u606F]
10
- ${i}`):i}sendPromptWithRetry(e,i){this.acpClient.send(i).catch(t=>{if(r.error("acp-adapter",`Prompt failed: ${g(t)}`),z(t)){const n=g(t),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=t instanceof Error?t.message:String(t);this.finishRun("failed",s)})}silentlyDiscardActiveRun(e){this.activeRun===e&&(this.activeRun=null,this.acpClient?.clearSettleTimer(),e.flushTimer&&(clearTimeout(e.flushTimer),e.flushTimer=null),e.idleTimer&&(clearTimeout(e.idleTimer),e.idleTimer=null),this.emit("eventDone",e.eventId))}async spawnProcess(){const e=[...this.config.args??[]];this.autoInjectArgs?.acp&&e.push("--acp"),this.autoInjectArgs?.model&&e.push("--model",this.autoInjectArgs.model),this.config.command==="gemini"&&!e.includes("--skip-trust")&&e.push("--skip-trust"),this.config.command.includes("qwen")&&!e.includes("--experimental-skills")&&e.push("--experimental-skills"),this.config.command==="kiro-cli"&&!e.includes("--trust-all-tools")&&e.push("--trust-all-tools"),(this.config.command==="copilot"||this.config.command.endsWith("/copilot")||this.config.command==="gh"&&e.includes("copilot"))&&(e.includes("--allow-all-tools")||e.push("--allow-all-tools"),e.includes("--allow-all-paths")||e.push("--allow-all-paths")),this.agentProcess=new T(this.bridgeLog);const t=this.resolveCwd();try{if(!(await v.stat(t)).isDirectory())throw new Error(`Bound path is not a directory: ${t}`)}catch(s){throw String(s?.code??"")==="ENOENT"?new Error(`Bound directory does not exist: ${t}. Please rebind with /grix open <valid-directory>.`):s}await k(this.config.command,t,this.config.env).catch(s=>{r.warn("acp-adapter",`native provider config skipped: ${s instanceof Error?s.message:String(s)}`)});try{await this.agentProcess.start({command:this.config.command,args:e.length>0?e:void 0,cwd:t,env:this.config.env})}catch(s){throw r.error("acp-adapter",`Failed to spawn agent process: ${s}`),this.rejectDeferredEvents(`agent spawn failed: ${s instanceof Error?s.message:String(s)}`),this.emit("exit",null),s}r.info("acp-adapter","ACP agent process started"),this.agentProcess.on("error",s=>{this.stopped||(r.error("acp-adapter",`Agent process error: ${s.message}`),this.activeRun&&this.finishRun("failed",`agent process error: ${s.message}`),this.rejectDeferredEvents(`agent process error: ${s.message}`),this.emit("exit",null))}),this.agentProcess.on("exit",s=>{this.stopped||(r.error("acp-adapter",`Agent process exited unexpectedly (code=${s})`),this.activeRun&&this.finishRun("failed",`agent process exited (code=${s})`),this.rejectDeferredEvents(`agent process exited (code=${s})`),this.emit("exit",s))}),this.agentProcess.on("stderr",()=>{this.activeRun&&this.resetIdleTimer(this.activeRun)})}async connectSession(e){if(this.sessionConnected||!this.agentProcess?.transport)return;let i;this.acpMcpTools&&(i=await this.startInternalApiAndMcp()),this.acpClient=new $,this.acpClient.on("event",o=>this.handleAcpEvent(o)),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 t=this.currentAibotSessionId&&this.bindingStore?this.bindingStore.getAcpSessionId(this.currentAibotSessionId):void 0,s=async o=>{await this.acpClient.connect({transport:this.agentProcess.transport,authMethod:this.acpAuthMethod,initialMode:this.acpInitialMode,initialModel:this.acpInitialModel,cwd:e||this.resolveCwd(),mcpServers:i,sessionId:o})};let n=!1,a;try{await s(t)}catch(o){if(o instanceof P){await this.handleAuthRequired(o);return}if(!t)throw this.handleConnectFailure(o),o;if(r.warn("acp-adapter",`Failed to load persisted session ${t}, fallback to session/new: ${g(o)}`),n=!0,a=t,this.config.command==="kiro-cli"&&this.currentAibotSessionId){r.info("acp-adapter",`Building kiro recovery summary: aibotSession=${this.currentAibotSessionId} acpSession=${t}`);const c=await this.buildKiroRecoveryContext(this.currentAibotSessionId,t,e||this.resolveCwd());c?(this.recoveryContextBySessionId.set(this.currentAibotSessionId,c),r.info("acp-adapter",`Recovery context prepared for session ${this.currentAibotSessionId} (chars=${c.length})`)):r.warn("acp-adapter",`Recovery context skipped: no summary generated for acpSession=${t}`)}await s(void 0)}this.sessionConnected=!0,r.info("acp-adapter",`ACP session ready: ${this.acpClient.sessionId}`),this.emit("acpSessionReady",this.acpClient.sessionId),this.currentAibotSessionId&&this.bindingStore&&this.bindingStore.setAcpSessionId(this.currentAibotSessionId,this.acpClient.sessionId);for(const[o,c]of this.sessionBindings){this.bindingStore&&this.acpClient.sessionId&&this.bindingStore.setAcpSessionId(o,this.acpClient.sessionId);const d=n?"binding_recreated":"binding_ready",l=this.buildToolbarContext(d,c);n&&a&&(l.previous_session_id=a,l.recreated_session_id=this.acpClient.sessionId),this.callbacks.sendUpdateBindingCard(o,"ready",c,l)}}async buildKiroRecoveryContext(e,i,t){try{const s=f.join(C(),".kiro","sessions","cli",`${i}.json`);await v.access(s),r.info("acp-adapter",`Reading kiro source session file: ${s}`);const n=await this.generateSummaryByKiroCli(s,t);if(!n)return;const a=f.join(C(),".grix","data","session-summaries");await v.mkdir(a,{recursive:!0});const o=f.join(a,`${e}.md`),c=[`SOURCE_KIRO_SESSION_FILE: ${s}`,"",n.trim(),""].join(`
10
+ ${i}`):i}sendPromptWithRetry(e,i){this.acpClient.send(i).catch(t=>{if(r.error("acp-adapter",`Prompt failed: ${g(t)}`),z(t)){const n=g(t),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=t instanceof Error?t.message:String(t);this.finishRun("failed",s)})}silentlyDiscardActiveRun(e){this.activeRun===e&&(this.activeRun=null,this.acpClient?.clearSettleTimer(),e.flushTimer&&(clearTimeout(e.flushTimer),e.flushTimer=null),e.idleTimer&&(clearTimeout(e.idleTimer),e.idleTimer=null),this.emit("eventDone",e.eventId))}async spawnProcess(){const e=[...this.config.args??[]];this.autoInjectArgs?.acp&&e.push("--acp"),this.autoInjectArgs?.model&&e.push("--model",this.autoInjectArgs.model),this.config.command==="gemini"&&!e.includes("--skip-trust")&&e.push("--skip-trust"),this.config.command.includes("qwen")&&!e.includes("--experimental-skills")&&e.push("--experimental-skills"),this.config.command==="kiro-cli"&&!e.includes("--trust-all-tools")&&e.push("--trust-all-tools"),(this.config.command==="copilot"||this.config.command.endsWith("/copilot")||this.config.command==="gh"&&e.includes("copilot"))&&(e.includes("--allow-all-tools")||e.push("--allow-all-tools"),e.includes("--allow-all-paths")||e.push("--allow-all-paths")),this.agentProcess=new T(this.bridgeLog);const t=this.resolveCwd();try{if(!(await v.stat(t)).isDirectory())throw new Error(`Bound path is not a directory: ${t}`)}catch(s){throw String(s?.code??"")==="ENOENT"?new Error(`Bound directory does not exist: ${t}. Please rebind with /grix open <valid-directory>.`):s}await k(this.config.command,t,this.config.env).catch(s=>{r.warn("acp-adapter",`native provider config skipped: ${s instanceof Error?s.message:String(s)}`)});try{await this.agentProcess.start({command:this.config.command,args:e.length>0?e:void 0,cwd:t,env:this.config.env})}catch(s){throw r.error("acp-adapter",`Failed to spawn agent process: ${s}`),this.rejectDeferredEvents(`agent spawn failed: ${s instanceof Error?s.message:String(s)}`),this.emit("exit",null),s}r.info("acp-adapter","ACP agent process started"),this.agentProcess.on("error",s=>{this.stopped||(r.error("acp-adapter",`Agent process error: ${s.message}`),this.activeRun&&this.finishRun("failed",`agent process error: ${s.message}`),this.rejectDeferredEvents(`agent process error: ${s.message}`),this.emit("exit",null))}),this.agentProcess.on("exit",s=>{this.stopped||(r.error("acp-adapter",`Agent process exited unexpectedly (code=${s})`),this.activeRun&&this.finishRun("failed",`agent process exited (code=${s})`),this.rejectDeferredEvents(`agent process exited (code=${s})`),this.emit("exit",s))}),this.agentProcess.on("stderr",()=>{this.activeRun&&this.resetIdleTimer(this.activeRun)})}async connectSession(e){if(this.sessionConnected||!this.agentProcess?.transport)return;let i;this.acpMcpTools&&(i=await this.startInternalApiAndMcp()),this.acpClient=new $,this.acpClient.on("event",o=>this.handleAcpEvent(o)),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 t=this.currentAibotSessionId&&this.bindingStore?this.bindingStore.getAcpSessionId(this.currentAibotSessionId):void 0,s=async o=>{await this.acpClient.connect({transport:this.agentProcess.transport,authMethod:this.acpAuthMethod,initialMode:this.acpInitialMode,initialModel:this.acpInitialModel,cwd:e||this.resolveCwd(),mcpServers:i,sessionId:o})};let n=!1,a;try{await s(t)}catch(o){if(o instanceof P){await this.handleAuthRequired(o);return}if(!t)throw this.handleConnectFailure(o),o;if(r.warn("acp-adapter",`Failed to load persisted session ${t}, fallback to session/new: ${g(o)}`),n=!0,a=t,this.config.command==="kiro-cli"&&this.currentAibotSessionId){r.info("acp-adapter",`Building kiro recovery summary: aibotSession=${this.currentAibotSessionId} acpSession=${t}`);const c=await this.buildKiroRecoveryContext(this.currentAibotSessionId,t,e||this.resolveCwd());c?(this.recoveryContextBySessionId.set(this.currentAibotSessionId,c),r.info("acp-adapter",`Recovery context prepared for session ${this.currentAibotSessionId} (chars=${c.length})`)):r.warn("acp-adapter",`Recovery context skipped: no summary generated for acpSession=${t}`)}await s(void 0)}this.sessionConnected=!0,r.info("acp-adapter",`ACP session ready: ${this.acpClient.sessionId}`),this.emit("acpSessionReady",this.acpClient.sessionId),this.currentAibotSessionId&&this.bindingStore&&this.bindingStore.setAcpSessionId(this.currentAibotSessionId,this.acpClient.sessionId);for(const[o,c]of this.sessionBindings){this.bindingStore&&this.acpClient.sessionId&&this.bindingStore.setAcpSessionId(o,this.acpClient.sessionId);const l=n?"binding_recreated":"binding_ready",d=this.buildToolbarContext(l,c);n&&a&&(d.previous_session_id=a,d.recreated_session_id=this.acpClient.sessionId),this.callbacks.sendUpdateBindingCard(o,"ready",c,d)}}async buildKiroRecoveryContext(e,i,t){try{const s=f.join(C(),".kiro","sessions","cli",`${i}.json`);await v.access(s),r.info("acp-adapter",`Reading kiro source session file: ${s}`);const n=await this.generateSummaryByKiroCli(s,t);if(!n)return;const a=f.join(C(),".grix","data","session-summaries");await v.mkdir(a,{recursive:!0});const o=f.join(a,`${e}.md`),c=[`SOURCE_KIRO_SESSION_FILE: ${s}`,"",n.trim(),""].join(`
11
11
  `);return await v.writeFile(o,c,"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(`
12
12
  `)}catch(s){r.warn("acp-adapter",`Failed to build kiro recovery context: ${g(s)}`);return}}async generateSummaryByKiroCli(e,i){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(`
13
13
  `)],n=await this.runCommandCapture("kiro-cli",s,i,45e3);if(!n)return;const a=V(n).trim(),o=a.indexOf("> "),c=o>=0?a.slice(o+2).trim():a;if(c)return r.info("acp-adapter",`Kiro summary generated from ${e} (raw_chars=${a.length}, body_chars=${c.length})`),c.split(`
14
- `).filter(d=>!d.includes("Credits:")&&!d.includes("Time:")).join(`
15
- `).trim()}runCommandCapture(e,i,t,s){return new Promise((n,a)=>{const o=R(e,i,{cwd:t,stdio:["ignore","pipe","pipe"]});let c="",d="";const l=setTimeout(()=>{o.kill("SIGTERM"),a(new Error(`${e} timed out after ${s}ms`))},s);o.stdout.on("data",p=>{c+=String(p)}),o.stderr.on("data",p=>{d+=String(p)}),o.on("error",p=>{clearTimeout(l),a(p)}),o.on("close",p=>{if(clearTimeout(l),p===0){n(c||d);return}a(new Error(`${e} exited with code ${p}: ${d||c}`))})})}handleConnectFailure(e){r.error("acp-adapter",`ACP session creation failed: ${g(e)}`),this.acpClient?.removeAllListeners(),this.acpClient=null,this.emit("exit",null)}async startInternalApiAndMcp(){try{this.internalApi||(this.internalApi=new M,this.internalApi.setInvokeHandler(async(n,a)=>this.callbacks.agentInvoke(n,a)),await this.internalApi.start(0),r.info("acp-adapter",`Internal API started at ${this.internalApi.url}`)),this.internalApi.setMcpBridgeUpHandler(n=>this.onMcpBridgeUp(n));const e=this.config.command??"",i=e.includes("qwen")?".qwen":e.includes("gemini")?".gemini":e.includes("kiro")?".kiro":this.isReasonix?".reasonix":void 0;if(i){const n=f.join(C(),i,"skills"),a=_(n);a.length>0&&r.info("acp-adapter",`Synced connector skills to ${n}: [${a.join(", ")}]`)}const t=f.resolve(b,"../../mcp/mcp-bridge-server.js"),s=this.internalApi.mcpBridgeWsUrl;return[{name:"grix-app-bridge",command:process.execPath,args:[t,"--ws-url",s],...!this.isReasonix&&{env:{GRIX_CONNECTOR_MCP_BRIDGE_WS:s}}}]}catch(e){r.error("acp-adapter",`Failed to start MCP tools: ${e}`);return}}onMcpBridgeUp(e){this.callbacks.sendMcpFrame?.(e)}deliverMcpFrameToAgent(e){this.internalApi?.sendMcpFrameToBridge(e)}cleanup(){this.activeRun,this.pendingApprovals.clear(),this.pendingAutoCompact=!1,this.rejectDeferredEvents("adapter cleaned up"),this.cancelAllDeferredTimers(),this.sessionConnected=!1,this.acpClient&&(this.acpClient.clearSettleTimer(),this.acpClient.removeAllListeners(),this.acpClient=null),this.agentProcess&&(this.agentProcess.removeAllListeners(),this.agentProcess=null)}async handleAuthRequired(e){r.info("acp-adapter",`Auth required, methods: ${e.authMethods.map(n=>n.id).join(", ")}`);const i=e.authMethods.find(n=>/oauth|browser/i.test(n.id))??e.authMethods[0];if(!i)throw e;const t=this.currentAibotSessionId??"";this.callbacks.sendAuthNotification(t,`Authentication required (${i.id}). Initiating auth flow...`);for(const[n,a]of this.sessionBindings)this.callbacks.sendUpdateBindingCard(n,"failed",a);const s=await this.captureAuthUrl();s&&this.callbacks.sendAuthNotification(t,`Please open this URL to authenticate:
14
+ `).filter(l=>!l.includes("Credits:")&&!l.includes("Time:")).join(`
15
+ `).trim()}runCommandCapture(e,i,t,s){return new Promise((n,a)=>{const o=R(e,i,{cwd:t,stdio:["ignore","pipe","pipe"]});let c="",l="";const d=setTimeout(()=>{o.kill("SIGTERM"),a(new Error(`${e} timed out after ${s}ms`))},s);o.stdout.on("data",p=>{c+=String(p)}),o.stderr.on("data",p=>{l+=String(p)}),o.on("error",p=>{clearTimeout(d),a(p)}),o.on("close",p=>{if(clearTimeout(d),p===0){n(c||l);return}a(new Error(`${e} exited with code ${p}: ${l||c}`))})})}handleConnectFailure(e){r.error("acp-adapter",`ACP session creation failed: ${g(e)}`),this.acpClient?.removeAllListeners(),this.acpClient=null,this.emit("exit",null)}async startInternalApiAndMcp(){try{this.internalApi||(this.internalApi=new M,this.internalApi.setInvokeHandler(async(a,o,c,l)=>this.callbacks.agentInvoke(a,o,l)),await this.internalApi.start(0),r.info("acp-adapter",`Internal API started at ${this.internalApi.url}`)),this.internalApi.setMcpBridgeUpHandler(a=>this.onMcpBridgeUp(a));const e=this.config.command??"",i=e.includes("qwen")?".qwen":e.includes("gemini")?".gemini":e.includes("kiro")?".kiro":this.isReasonix?".reasonix":void 0;if(i){const a=f.join(C(),i,"skills"),o=_(a);o.length>0&&r.info("acp-adapter",`Synced connector skills to ${a}: [${o.join(", ")}]`)}const t=f.resolve(I,"../../mcp/mcp-bridge-server.js"),s=this.internalApi.mcpBridgeWsUrl,n=f.resolve(I,"../../mcp/acp-mcp-server.js");return[{name:"grix-app-bridge",command:process.execPath,args:[t,"--ws-url",s],...!this.isReasonix&&{env:{GRIX_CONNECTOR_MCP_BRIDGE_WS:s}}},{name:"grix-connector-tools",command:process.execPath,args:[n,"--api-url",this.internalApi.url]}]}catch(e){r.error("acp-adapter",`Failed to start MCP tools: ${e}`);return}}onMcpBridgeUp(e){this.callbacks.sendMcpFrame?.(e)}deliverMcpFrameToAgent(e){this.internalApi?.sendMcpFrameToBridge(e)}cleanup(){this.activeRun,this.pendingApprovals.clear(),this.pendingAutoCompact=!1,this.rejectDeferredEvents("adapter cleaned up"),this.cancelAllDeferredTimers(),this.sessionConnected=!1,this.acpClient&&(this.acpClient.clearSettleTimer(),this.acpClient.removeAllListeners(),this.acpClient=null),this.agentProcess&&(this.agentProcess.removeAllListeners(),this.agentProcess=null)}async handleAuthRequired(e){r.info("acp-adapter",`Auth required, methods: ${e.authMethods.map(n=>n.id).join(", ")}`);const i=e.authMethods.find(n=>/oauth|browser/i.test(n.id))??e.authMethods[0];if(!i)throw e;const t=this.currentAibotSessionId??"";this.callbacks.sendAuthNotification(t,`Authentication required (${i.id}). Initiating auth flow...`);for(const[n,a]of this.sessionBindings)this.callbacks.sendUpdateBindingCard(n,"failed",a);const s=await this.captureAuthUrl();s&&this.callbacks.sendAuthNotification(t,`Please open this URL to authenticate:
16
16
  ${s}
17
17
 
18
18
  Waiting for authentication to complete...`);try{await this.acpClient.authenticate(i.id),r.info("acp-adapter","Authentication successful"),this.callbacks.sendAuthNotification(t,"Authentication successful. Resuming...");const n=this.acpMcpTools?await this.startInternalApiAndMcp():void 0;await this.acpClient.connect({transport:this.agentProcess.transport,initialMode:this.acpInitialMode,initialModel:this.acpInitialModel,cwd:this.resolveCwd(),mcpServers:n}),r.info("acp-adapter",`ACP session ready after auth: ${this.acpClient.sessionId}`),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(n){throw r.error("acp-adapter",`Auth retry failed: ${n}`),n}}captureAuthUrl(){return new Promise(e=>{if(!this.agentProcess){e(null);return}const i=/https?:\/\/[^\s"')\]]+/;let t=!1;const s=n=>{if(t)return;const o=n.toString().replace(/\x1b\[[0-9;]*m/g,"").match(i);o&&(t=!0,this.agentProcess.removeListener("stderr",s),e(o[0]))};this.agentProcess.on("stderr",s),setTimeout(()=>{t||(t=!0,this.agentProcess.removeListener("stderr",s),e(null))},3e4)})}handleAcpEvent(e){if(e.type===m.PermissionRequest){this.activeRun&&this.resetIdleTimer(this.activeRun),this.handlePermissionRequest(e);return}if(e.type===m.ContextWindowUpdate){e.contextWindow&&this.callbacks.onContextWindowUpdated?.(e.contextWindow),this.compacting?this.finishCompaction("context-window-update"):e.contextWindow&&this.maybeScheduleAutoCompact(e.contextWindow);return}const i=this.activeRun;if(!i)return;i.responded||(i.responded=!0);let t=!1;switch(e.type){case m.Text:{if(e.content){t=!0;const s=i.quotedStream.consume(e.content);s.quotedMessageId&&(i.quotedMessageId=s.quotedMessageId),s.deltaContent&&this.appendToStream(i,s.deltaContent)}break}case m.ToolUse:{t=!0,i.awaitingToolResult=!0,e.toolName&&(this.emitRawEventEnvelope(i,{type:"tool_use",payload:{tool_name:e.toolName,tool_input:e.toolInput??""}})||this.callbacks.sendToolUse(i.eventId,i.sessionId,e.toolName,e.toolInput??""));break}case m.ToolResult:{i.awaitingToolResult=!1,e.content&&(t=!0,this.emitRawEventEnvelope(i,{type:"tool_result",payload:{tool_name:e.toolName??"",content:e.content}})||this.callbacks.sendToolResult(i.eventId,i.sessionId,e.toolName??"",e.content));break}case m.ToolProgress:{t=!0,e.content&&(this.emitRawEventEnvelope(i,{type:"tool_progress",payload:{tool_name:e.toolName??"",content:e.content}})||this.callbacks.sendToolResult(i.eventId,i.sessionId,e.toolName??"",e.content));break}case m.Thinking:{e.content&&(t=!0,this.emitRawEventEnvelope(i,{type:"thinking",payload:{content:e.content}})||this.callbacks.sendThinking(i.eventId,i.sessionId,e.content));break}case m.Error:{const s=String(e.error??"unknown error");this.emitRawEventEnvelope(i,{type:"error",payload:{message:s}}),r.error("acp-adapter",`ACP error: ${s}`),this.callbacks.sendRunError(i.eventId,i.sessionId,s);break}case m.Result:{this.emitRawEventEnvelope(i,{type:"result",payload:{done:e.done??!0}});const s=i.quotedStream.flush();s.deltaContent&&this.appendToStream(i,s.deltaContent),s.quotedMessageId&&(i.quotedMessageId=s.quotedMessageId),this.flushStream(),this.finishRun("responded");break}}t&&(i.lastProgressAt=Date.now(),i.idleNoProgressCount=0),this.resetIdleTimer(i)}handlePermissionRequest(e){const i=e.permissionRequest;if(!i||!e.requestId||!this.acpClient)return;if(this.approvalMode==="yolo"||this.approvalMode==="autoEdit"){r.info("acp-adapter",`Auto-approving (${this.approvalMode}): ${i.toolName}`),this.acpClient.respondPermission(i.requestId,{behavior:"allow"}).catch(()=>{});return}const t=i.toolCallId;this.pendingApprovals.set(t,e.requestId);const s=this.activeRun;s?(s.idleTimer&&(clearTimeout(s.idleTimer),s.idleTimer=null),this.emitRawEventEnvelope(s,{type:"permission_request",payload:{request_id:i.requestId,tool_call_id:t,tool_name:i.toolName,tool_title:i.toolTitle,options:i.options}})||this.callbacks.sendPermissionCard({eventId:s.eventId,sessionId:s.sessionId,toolCallId:t,toolName:i.toolName,toolTitle:i.toolTitle,options:i.options})):(r.info("acp-adapter",`Permission request without active run, auto-approving: ${i.toolName}`),this.acpClient.respondPermission(e.requestId,{behavior:"allow"}),this.pendingApprovals.delete(t))}emitRawEventEnvelope(e,i){return!e||!this.rawTransport||!this.callbacks.sendRawEventEnvelope?!1:(this.callbacks.sendRawEventEnvelope(e.eventId,e.sessionId,{type:i.type,payload:i.payload,seq:++this.rawEventSeq,at:new Date().toISOString()}),!0)}resetIdleTimer(e){e.idleTimer&&clearTimeout(e.idleTimer);const i=e.awaitingToolResult?H:K;e.idleTimer=setTimeout(()=>{if(this.activeRun?.eventId!==e.eventId)return;const t=this.acpClient;t?.isAlive?t.ping(5e3).then(async s=>{if(this.activeRun?.eventId!==e.eventId)return;if(!s){r.error("acp-adapter",`Ping failed, declaring stuck: ${e.eventId}`),this.finishRun("failed","agent unreachable"),this.declareStuck();return}if(e.awaitingToolResult){r.info("acp-adapter",`Idle timer: tool running, ping ok, continuing: ${e.eventId}`),e.idleNoProgressCount=0,this.resetIdleTimer(e);return}const n=e.lastIdleCheckAt,a=e.lastProgressAt>n,o=a?!1:await B(t.sessionId,n),c=a||o;e.lastIdleCheckAt=Date.now(),c?(o&&r.info("acp-adapter",`Idle timer: no stream progress but kiro session active, continuing: ${e.eventId}`),e.idleNoProgressCount=0,this.resetIdleTimer(e)):(e.idleNoProgressCount++,e.idleNoProgressCount>=2?(r.error("acp-adapter",`Agent alive but no progress for ${e.idleNoProgressCount} idle cycles, declaring stuck: ${e.eventId}`),this.finishRun("failed",`agent alive but no progress for ${e.idleNoProgressCount} idle cycles`),this.declareStuck()):(r.warn("acp-adapter",`Idle timer fired, ping ok, no progress (${e.idleNoProgressCount}/2): ${e.eventId}`),this.resetIdleTimer(e)))}).catch(()=>{this.activeRun?.eventId===e.eventId&&(r.error("acp-adapter",`Ping error, declaring stuck: ${e.eventId}`),this.finishRun("failed","agent unreachable"),this.declareStuck())}):(r.error("acp-adapter",`Agent idle (no client), declaring stuck: ${e.eventId}`),this.finishRun("failed","agent unreachable"),this.declareStuck())},i)}declareStuck(){this.acpClient&&(this.acpClient.clearSettleTimer(),this.acpClient.removeAllListeners(),this.acpClient=null),this.agentProcess&&(this.agentProcess.close().catch(()=>{}),this.agentProcess=null),this.emit("stuck")}appendToStream(e,i){e.buffer+=i,e.flushTimer||(e.flushTimer=setTimeout(()=>this.flushStream(),L))}emitSegmentedStream(e,i){if(!i)return;const t=e.markdownSegmenter.push(i);for(const s of t)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"}`)))}flushStream(){const e=this.activeRun;if(!e||!e.buffer)return;e.flushTimer&&(clearTimeout(e.flushTimer),e.flushTimer=null);const i=e.buffer;e.buffer="",this.emitSegmentedStream(e,i)}finishRun(e,i){const t=this.activeRun;if(!t)return;this.activeRun=null,this.acpClient?.clearSettleTimer(),this.emit("eventDone",t.eventId),t.flushTimer&&(clearTimeout(t.flushTimer),t.flushTimer=null),t.idleTimer&&(clearTimeout(t.idleTimer),t.idleTimer=null);const s=t.quotedStream.flush();s.deltaContent&&(t.buffer+=s.deltaContent),s.quotedMessageId&&(t.quotedMessageId=s.quotedMessageId),i&&(e==="failed"?(r.error("acp-adapter",`finishRun failed: ${i} event=${t.eventId}`),t.buffer+=`
19
19
 
20
- The task was interrupted. Please resend your instruction.`):r.info("acp-adapter",`finishRun ${e}: ${i} event=${t.eventId}`)),t.buffer&&(this.emitSegmentedStream(t,t.buffer),t.buffer="");const n=++t.chunkSeq;if(this.callbacks.sendFinalStreamChunkReliable){let o;const c=this.callbacks.sendFinalStreamChunkReliable(t.eventId,t.sessionId,n,t.currentClientMsgId);c.catch(()=>{});const d=new Promise((l,p)=>{o=setTimeout(()=>p(new Error("final chunk ACK timeout")),5e3),o.unref()});Promise.race([c,d]).then(()=>{clearTimeout(o),t.silent||this.callbacks.sendEventResult(t.eventId,e,i),this.persistEventResult(t,e,i),this.replayNextDeferredEvent(),this.tryRunPendingAutoCompact()}).catch(l=>{clearTimeout(o),r.warn("acp-adapter",`finalStreamChunk ACK timeout/failed event=${t.eventId}: ${l instanceof Error?l.message:l}`),this.callbacks.sendStreamChunk(t.eventId,t.sessionId,"",n,!0,t.currentClientMsgId),t.silent||this.callbacks.sendEventResult(t.eventId,e,i),this.persistEventResult(t,e,i),this.replayNextDeferredEvent(),this.tryRunPendingAutoCompact()})}else this.callbacks.sendStreamChunk(t.eventId,t.sessionId,"",n,!0,t.currentClientMsgId),t.silent||this.callbacks.sendEventResult(t.eventId,e,i),this.persistEventResult(t,e,i),this.replayNextDeferredEvent(),this.tryRunPendingAutoCompact()}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 i=this.getContextWindowUsedPercentage(e);i===null||i<y||this.pendingAutoCompact||this.compacting||(this.pendingAutoCompact=!0,r.info("acp-adapter",`[auto-compact] context_window usedPercentage=${i.toFixed(1)}% >= ${y}%, 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,i,t){this.eventResults&&!e.silent&&this.eventResults.set({sessionId:e.sessionId,eventId:e.eventId,status:i,msg:t,updatedAt:Date.now()})}}class Q extends I{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{ve as AcpAdapter};
20
+ The task was interrupted. Please resend your instruction.`):r.info("acp-adapter",`finishRun ${e}: ${i} event=${t.eventId}`)),t.buffer&&(this.emitSegmentedStream(t,t.buffer),t.buffer="");const n=++t.chunkSeq;if(this.callbacks.sendFinalStreamChunkReliable){let o;const c=this.callbacks.sendFinalStreamChunkReliable(t.eventId,t.sessionId,n,t.currentClientMsgId);c.catch(()=>{});const l=new Promise((d,p)=>{o=setTimeout(()=>p(new Error("final chunk ACK timeout")),5e3),o.unref()});Promise.race([c,l]).then(()=>{clearTimeout(o),t.silent||this.callbacks.sendEventResult(t.eventId,e,i),this.persistEventResult(t,e,i),this.replayNextDeferredEvent(),this.tryRunPendingAutoCompact()}).catch(d=>{clearTimeout(o),r.warn("acp-adapter",`finalStreamChunk ACK timeout/failed event=${t.eventId}: ${d instanceof Error?d.message:d}`),this.callbacks.sendStreamChunk(t.eventId,t.sessionId,"",n,!0,t.currentClientMsgId),t.silent||this.callbacks.sendEventResult(t.eventId,e,i),this.persistEventResult(t,e,i),this.replayNextDeferredEvent(),this.tryRunPendingAutoCompact()})}else this.callbacks.sendStreamChunk(t.eventId,t.sessionId,"",n,!0,t.currentClientMsgId),t.silent||this.callbacks.sendEventResult(t.eventId,e,i),this.persistEventResult(t,e,i),this.replayNextDeferredEvent(),this.tryRunPendingAutoCompact()}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 i=this.getContextWindowUsedPercentage(e);i===null||i<y||this.pendingAutoCompact||this.compacting||(this.pendingAutoCompact=!0,r.info("acp-adapter",`[auto-compact] context_window usedPercentage=${i.toFixed(1)}% >= ${y}%, 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,i,t){this.eventResults&&!e.silent&&this.eventResults.set({sessionId:e.sessionId,eventId:e.eventId,status:i,msg:t,updatedAt:Date.now()})}}class Q extends S{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{ve as AcpAdapter};
@@ -1,5 +1,5 @@
1
- import{spawn as P}from"node:child_process";import{randomUUID as $}from"node:crypto";import{EventEmitter as _}from"node:events";import{existsSync as E,readFileSync as S,readdirSync as k,statSync as w,mkdirSync as b,writeFileSync as x}from"node:fs";import{homedir as g}from"node:os";import{join as p,dirname as C,resolve as L}from"node:path";import{fileURLToPath as R}from"node:url";import{log as o}from"../../core/log/index.js";import{killProcessGroup as f}from"../../core/runtime/spawn.js";import{InternalApiServer as T}from"../../core/mcp/internal-api-server.js";import{syncDefaultSkillsToDir as j}from"../../default-skills/index.js";import{buildSimpleProbeReport as G}from"../shared/probe-util.js";const a="agy-adapter",m=p(g(),".gemini","antigravity-cli","log"),I=p(g(),".gemini","antigravity-cli","cache","last_conversations.json"),M=["\u4F60\u63A5\u5165\u7684\u662F Grix \u804A\u5929\u3002\u7528\u6237\u6D88\u606F\u88AB\u5305\u5728 <channel ...> \u6807\u7B7E\u91CC\uFF0C\u6807\u7B7E\u5C5E\u6027\u643A\u5E26\u672C\u8F6E\u4E0A\u4E0B\u6587\uFF1A","chat_id\uFF08\u5F53\u524D\u4F1A\u8BDD\uFF09\u3001event_id\uFF08\u672C\u8F6E\u4E8B\u4EF6\uFF09\u3001message_id\uFF08\u7528\u6237\u8FD9\u6761\u6D88\u606F\u7684 ID\uFF09\u3001user_id\uFF08\u53D1\u9001\u8005\uFF09\u3002","\u91CD\u8981\uFF1A\u4F60\u7684\u56DE\u590D\u5FC5\u987B\u8C03\u7528 grix \u7684 `grix_message_send` \u5DE5\u5177\u53D1\u9001\uFF08sessionId \u53D6\u81EA channel \u7684 chat_id\uFF0Ccontent \u4E3A\u4F60\u7684\u56DE\u590D\u5185\u5BB9\uFF09\uFF0C","\u4E0D\u8981\u53EA\u628A\u7B54\u6848\u6253\u5370\u5230\u6807\u51C6\u8F93\u51FA\u2014\u2014\u53EA\u6709\u8C03\u7528\u5DE5\u5177\u624D\u4F1A\u628A\u6D88\u606F\u771F\u6B63\u53D1\u7ED9\u7528\u6237\u3002","grix_message_send \u4F1A\u8FD4\u56DE\u4F60\u521A\u53D1\u51FA\u6D88\u606F\u7684 msg_id\u3002\u9700\u8981\u65F6\u4F60\u53EF\u4EE5\u7528\u8FD9\u4E2A ID \u505A\u540E\u7EED\u64CD\u4F5C\uFF0C\u4F8B\u5982\u8C03\u7528 grix_message_unsend\uFF08\u4F20 msgId\uFF09\u64A4\u56DE\u67D0\u6761\u6D88\u606F\u3002"].join(""),O=new Set(["send_msg","grix_reply"]),D=new Set(["send_msg","grix_reply","delete_msg","session_send","grix_complete","grix_composing","grix_event_ack"]);class X extends _{type="agy";config;callbacks;runtimeResolver;alive=!1;stopped=!1;internalApi=null;activeEventId=null;activeEventSessionId=null;activeProcess=null;completedEventIds=new Set;activeEventSentViaTool=!1;sessionConversationMap=new Map;conversationOutputCache=new Map;eventQueue=[];constructor(t,e,s){super(),this.config=t,this.callbacks=e,this.runtimeResolver=s}async start(){this.alive||(this.alive=!0,this.stopped=!1,await this.startInternalApiAndInjectMcp(),o.info(a,"AgyAdapter started"))}async startInternalApiAndInjectMcp(){try{this.internalApi=new T,this.internalApi.setInvokeHandler(async(r,l)=>this.handleToolInvoke(r,l)),await this.internalApi.start(0),o.info(a,`Internal API started at ${this.internalApi.url}`);const t=this.getMcpConfig(),e=p(g(),".gemini","config","mcp_config.json");b(C(e),{recursive:!0});let s={};try{E(e)&&(s=JSON.parse(S(e,"utf8")))}catch{}const i=s.mcpServers&&typeof s.mcpServers=="object"?s.mcpServers:{};i[t.name]={command:t.command,args:t.args},s.mcpServers=i,x(e,`${JSON.stringify(s,null,2)}
2
- `,"utf8"),o.info(a,`MCP config injected into ${e}`);const n=p(g(),".gemini","skills"),c=j(n);c.length>0&&o.info(a,`Synced connector skills to ${n}: [${c.join(", ")}]`)}catch(t){o.warn(a,`Failed to start MCP tools (non-fatal): ${t instanceof Error?t.message:String(t)}`)}}async handleToolInvoke(t,e){let s=e;D.has(t)&&this.activeEventSessionId&&(s.session_id==null||s.session_id==="")&&(s={...s,session_id:this.activeEventSessionId});const i=await this.callbacks.agentInvoke(t,s);return O.has(t)&&this.activeEventId&&(this.activeEventSentViaTool=!0),i}async stop(){if(!this.stopped){if(this.stopped=!0,this.alive=!1,this.activeProcess){try{f(this.activeProcess,"SIGKILL")}catch{}this.activeProcess=null}if(this.activeEventId&&this.activeEventSessionId)try{this.callbacks.sendEventResult(this.activeEventId,"canceled","adapter stopped")}catch{}if(this.activeEventId=null,this.activeEventSessionId=null,this.internalApi){try{await this.internalApi.stop()}catch{}this.internalApi=null}this.emit("exit",0),o.info(a,"AgyAdapter stopped")}}isAlive(){return this.alive}async createSession(t){return t.cwd??$()}async resumeSession(t,e){}async destroySession(t){}sendPrompt(t){const e=new _;return e.adapterSessionId=t.adapterSessionId,e.cancel=async()=>{e.emit("done",{status:"canceled"})},e}async cancel(t){}cancelCurrentRun(){if(!this.activeEventId||!this.activeProcess)return;const t=this.activeEventId;o.info(a,`cancelCurrentRun: killing process group for event ${t}`);try{f(this.activeProcess,"SIGKILL")}catch{}this.activeProcess=null,this.callbacks.sendEventResult(t,"canceled","restarted by user"),this.clearActiveEvent(t),this.drainQueue()}deliverInboundEvent(t){if(!this.stopped){if(this.completedEventIds.has(t.event_id)){o.debug(a,`Skipping duplicate event: ${t.event_id}`);return}if(this.callbacks.sendEventAck(t.event_id,t.session_id),this.activeEventId){o.info(a,`Queuing event ${t.event_id} (active: ${this.activeEventId})`),this.eventQueue.push(t);return}this.processEvent(t)}}deliverStopEvent(t,e){if(this.activeEventId===t&&this.activeProcess){o.info(a,`Stopping event ${t}`);try{f(this.activeProcess,"SIGKILL")}catch{}this.activeProcess=null,this.callbacks.sendEventResult(t,"canceled","stopped by user"),this.clearActiveEvent(t),this.drainQueue()}}setPermissionHandler(){}async ping(t){return this.alive}getStatus(){return{alive:this.alive,busy:this.activeEventId!==null,sessions:this.sessionConversationMap.size}}async probe(t){const e=this.getStatus();return G(this.config.command||"agy",{alive:e.alive,busy:e.busy,started:e.alive},t)}getActiveEventIds(){return this.activeEventId?[this.activeEventId]:[]}clearActiveEventForShutdown(){if(this.activeProcess)try{f(this.activeProcess,"SIGKILL")}catch{}this.activeEventId=null,this.activeEventSessionId=null,this.activeProcess=null,this.activeEventSentViaTool=!1}getMcpConfig(){if(!this.internalApi)return null;const t=L(R(import.meta.url),"../../../mcp/acp-mcp-server.js");return{name:"grix-connector-tools",command:process.execPath,args:[t,"--api-url",this.internalApi.url]}}processEvent(t){const{event_id:e,session_id:s}=t,i=this.runtimeResolver(s);if(!i.cwd){o.warn(a,`No working directory for session ${s}, event should have been intercepted by bridge`),this.callbacks.forceCompleteInternalEvent(e,s);return}this.activeEventId=e,this.activeEventSessionId=s,this.activeEventSentViaTool=!1,this.emit("eventStarted",e,s),o.info(a,`Processing event ${e} for session ${s}`);const n=this.buildAgyPrompt(t);this.spawnAgyPrint(e,s,n,i)}buildAgyPrompt(t){const s=[["chat_id",t.session_id],["event_id",t.event_id],["message_id",t.msg_id],["user_id",t.sender_id],["quoted_message_id",t.quoted_message_id]].filter(([,l])=>l!=null&&l!=="").map(([l,h])=>`${l}="${h}"`).join(" "),i=this.renderContextBlock(t),n=i?`${i}
1
+ import{spawn as P}from"node:child_process";import{randomUUID as $}from"node:crypto";import{EventEmitter as _}from"node:events";import{existsSync as E,readFileSync as S,readdirSync as k,statSync as w,mkdirSync as b,writeFileSync as x}from"node:fs";import{homedir as g}from"node:os";import{join as p,dirname as C,resolve as L}from"node:path";import{fileURLToPath as R}from"node:url";import{log as o}from"../../core/log/index.js";import{killProcessGroup as f}from"../../core/runtime/spawn.js";import{InternalApiServer as T}from"../../core/mcp/internal-api-server.js";import{syncDefaultSkillsToDir as j}from"../../default-skills/index.js";import{buildSimpleProbeReport as G}from"../shared/probe-util.js";const a="agy-adapter",m=p(g(),".gemini","antigravity-cli","log"),I=p(g(),".gemini","antigravity-cli","cache","last_conversations.json"),M=["\u4F60\u63A5\u5165\u7684\u662F Grix \u804A\u5929\u3002\u7528\u6237\u6D88\u606F\u88AB\u5305\u5728 <channel ...> \u6807\u7B7E\u91CC\uFF0C\u6807\u7B7E\u5C5E\u6027\u643A\u5E26\u672C\u8F6E\u4E0A\u4E0B\u6587\uFF1A","chat_id\uFF08\u5F53\u524D\u4F1A\u8BDD\uFF09\u3001event_id\uFF08\u672C\u8F6E\u4E8B\u4EF6\uFF09\u3001message_id\uFF08\u7528\u6237\u8FD9\u6761\u6D88\u606F\u7684 ID\uFF09\u3001user_id\uFF08\u53D1\u9001\u8005\uFF09\u3002","\u91CD\u8981\uFF1A\u4F60\u7684\u56DE\u590D\u5FC5\u987B\u8C03\u7528 grix \u7684 `grix_message_send` \u5DE5\u5177\u53D1\u9001\uFF08sessionId \u53D6\u81EA channel \u7684 chat_id\uFF0Ccontent \u4E3A\u4F60\u7684\u56DE\u590D\u5185\u5BB9\uFF09\uFF0C","\u4E0D\u8981\u53EA\u628A\u7B54\u6848\u6253\u5370\u5230\u6807\u51C6\u8F93\u51FA\u2014\u2014\u53EA\u6709\u8C03\u7528\u5DE5\u5177\u624D\u4F1A\u628A\u6D88\u606F\u771F\u6B63\u53D1\u7ED9\u7528\u6237\u3002","grix_message_send \u4F1A\u8FD4\u56DE\u4F60\u521A\u53D1\u51FA\u6D88\u606F\u7684 msg_id\u3002\u9700\u8981\u65F6\u4F60\u53EF\u4EE5\u7528\u8FD9\u4E2A ID \u505A\u540E\u7EED\u64CD\u4F5C\uFF0C\u4F8B\u5982\u8C03\u7528 grix_message_unsend\uFF08\u4F20 msgId\uFF09\u64A4\u56DE\u67D0\u6761\u6D88\u606F\u3002"].join(""),O=new Set(["send_msg","grix_reply"]),D=new Set(["send_msg","grix_reply","delete_msg","session_send","grix_complete","grix_composing","grix_event_ack"]);class X extends _{type="agy";config;callbacks;runtimeResolver;alive=!1;stopped=!1;internalApi=null;activeEventId=null;activeEventSessionId=null;activeProcess=null;completedEventIds=new Set;activeEventSentViaTool=!1;sessionConversationMap=new Map;conversationOutputCache=new Map;eventQueue=[];constructor(t,e,s){super(),this.config=t,this.callbacks=e,this.runtimeResolver=s}async start(){this.alive||(this.alive=!0,this.stopped=!1,await this.startInternalApiAndInjectMcp(),o.info(a,"AgyAdapter started"))}async startInternalApiAndInjectMcp(){try{this.internalApi=new T,this.internalApi.setInvokeHandler(async(r,l,h,u)=>this.handleToolInvoke(r,l,u)),await this.internalApi.start(0),o.info(a,`Internal API started at ${this.internalApi.url}`);const t=this.getMcpConfig(),e=p(g(),".gemini","config","mcp_config.json");b(C(e),{recursive:!0});let s={};try{E(e)&&(s=JSON.parse(S(e,"utf8")))}catch{}const i=s.mcpServers&&typeof s.mcpServers=="object"?s.mcpServers:{};i[t.name]={command:t.command,args:t.args},s.mcpServers=i,x(e,`${JSON.stringify(s,null,2)}
2
+ `,"utf8"),o.info(a,`MCP config injected into ${e}`);const n=p(g(),".gemini","skills"),c=j(n);c.length>0&&o.info(a,`Synced connector skills to ${n}: [${c.join(", ")}]`)}catch(t){o.warn(a,`Failed to start MCP tools (non-fatal): ${t instanceof Error?t.message:String(t)}`)}}async handleToolInvoke(t,e,s){let i=e;D.has(t)&&this.activeEventSessionId&&(i.session_id==null||i.session_id==="")&&(i={...i,session_id:this.activeEventSessionId});const n=await this.callbacks.agentInvoke(t,i,s);return O.has(t)&&this.activeEventId&&(this.activeEventSentViaTool=!0),n}async stop(){if(!this.stopped){if(this.stopped=!0,this.alive=!1,this.activeProcess){try{f(this.activeProcess,"SIGKILL")}catch{}this.activeProcess=null}if(this.activeEventId&&this.activeEventSessionId)try{this.callbacks.sendEventResult(this.activeEventId,"canceled","adapter stopped")}catch{}if(this.activeEventId=null,this.activeEventSessionId=null,this.internalApi){try{await this.internalApi.stop()}catch{}this.internalApi=null}this.emit("exit",0),o.info(a,"AgyAdapter stopped")}}isAlive(){return this.alive}async createSession(t){return t.cwd??$()}async resumeSession(t,e){}async destroySession(t){}sendPrompt(t){const e=new _;return e.adapterSessionId=t.adapterSessionId,e.cancel=async()=>{e.emit("done",{status:"canceled"})},e}async cancel(t){}cancelCurrentRun(){if(!this.activeEventId||!this.activeProcess)return;const t=this.activeEventId;o.info(a,`cancelCurrentRun: killing process group for event ${t}`);try{f(this.activeProcess,"SIGKILL")}catch{}this.activeProcess=null,this.callbacks.sendEventResult(t,"canceled","restarted by user"),this.clearActiveEvent(t),this.drainQueue()}deliverInboundEvent(t){if(!this.stopped){if(this.completedEventIds.has(t.event_id)){o.debug(a,`Skipping duplicate event: ${t.event_id}`);return}if(this.callbacks.sendEventAck(t.event_id,t.session_id),this.activeEventId){o.info(a,`Queuing event ${t.event_id} (active: ${this.activeEventId})`),this.eventQueue.push(t);return}this.processEvent(t)}}deliverStopEvent(t,e){if(this.activeEventId===t&&this.activeProcess){o.info(a,`Stopping event ${t}`);try{f(this.activeProcess,"SIGKILL")}catch{}this.activeProcess=null,this.callbacks.sendEventResult(t,"canceled","stopped by user"),this.clearActiveEvent(t),this.drainQueue()}}setPermissionHandler(){}async ping(t){return this.alive}getStatus(){return{alive:this.alive,busy:this.activeEventId!==null,sessions:this.sessionConversationMap.size}}async probe(t){const e=this.getStatus();return G(this.config.command||"agy",{alive:e.alive,busy:e.busy,started:e.alive},t)}getActiveEventIds(){return this.activeEventId?[this.activeEventId]:[]}clearActiveEventForShutdown(){if(this.activeProcess)try{f(this.activeProcess,"SIGKILL")}catch{}this.activeEventId=null,this.activeEventSessionId=null,this.activeProcess=null,this.activeEventSentViaTool=!1}getMcpConfig(){if(!this.internalApi)return null;const t=L(R(import.meta.url),"../../../mcp/acp-mcp-server.js");return{name:"grix-connector-tools",command:process.execPath,args:[t,"--api-url",this.internalApi.url]}}processEvent(t){const{event_id:e,session_id:s}=t,i=this.runtimeResolver(s);if(!i.cwd){o.warn(a,`No working directory for session ${s}, event should have been intercepted by bridge`),this.callbacks.forceCompleteInternalEvent(e,s);return}this.activeEventId=e,this.activeEventSessionId=s,this.activeEventSentViaTool=!1,this.emit("eventStarted",e,s),o.info(a,`Processing event ${e} for session ${s}`);const n=this.buildAgyPrompt(t);this.spawnAgyPrint(e,s,n,i)}buildAgyPrompt(t){const s=[["chat_id",t.session_id],["event_id",t.event_id],["message_id",t.msg_id],["user_id",t.sender_id],["quoted_message_id",t.quoted_message_id]].filter(([,l])=>l!=null&&l!=="").map(([l,h])=>`${l}="${h}"`).join(" "),i=this.renderContextBlock(t),n=i?`${i}
3
3
 
4
4
  ${t.content}`:t.content,c=`<channel source="grix-agy" ${s}>
5
5
  ${n}