grix-connector 3.23.0 → 3.24.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/dist/adapter/acp/acp-adapter.js +14 -12
- package/dist/adapter/claude/claude-adapter.js +18 -18
- package/dist/adapter/claude/claude-bridge-server.js +1 -1
- package/dist/adapter/claude/claude-tools.js +1 -1
- package/dist/adapter/claude/claude-worker-client.js +1 -1
- package/dist/adapter/claude/mcp-http-launcher.js +2 -2
- package/dist/adapter/claude/protocol-contract.js +1 -1
- package/dist/adapter/claude/result-timeout.js +1 -1
- package/dist/adapter/opencode/format-error.js +1 -0
- package/dist/adapter/opencode/opencode-adapter.js +6 -6
- package/dist/adapter/opencode/opencode-transport.js +1 -1
- package/dist/adapter/opencode/opencode-types.js +1 -0
- package/dist/adapter/shared/session-context.js +2 -0
- package/dist/bridge/acp-toolbar-persist.js +1 -1
- package/dist/bridge/adapter-pool.js +1 -1
- package/dist/bridge/bridge.js +10 -11
- package/dist/core/access/allowlist-store.js +1 -1
- package/dist/core/aibot/binding-card-meta-cache.js +1 -1
- package/dist/core/config/claude-direct-env.js +1 -1
- package/dist/core/config/provider-env.js +1 -1
- package/dist/core/file-ops/list-files.js +1 -1
- package/dist/core/persistence/agent-global-config-store.js +1 -1
- package/dist/core/persistence/session-binding-store.js +1 -1
- package/dist/core/protocol/interaction-parser.js +1 -1
- package/dist/core/provider-quota/index.js +1 -1
- package/dist/core/provider-quota/providers.js +2 -2
- package/dist/default-skills/grix-agent-dispatch/SKILL.md +130 -58
- package/dist/default-skills/grix-owner-relay/SKILL.md +7 -0
- package/dist/log.js +2 -2
- package/dist/manager.js +1 -1
- package/dist/mcp/stream-http/config.js +1 -1
- package/dist/mcp/stream-http/connection-binding.js +1 -1
- package/dist/mcp/stream-http/security.js +1 -1
- package/dist/mcp/stream-http/tool-executor.js +1 -1
- package/dist/mcp/stream-http/tool-registry.js +1 -1
- package/dist/mcp/stream-http/tool-schemas.js +1 -1
- package/openclaw-plugin/index.js +26 -2
- package/package.json +3 -1
- package/scripts/analyze-audit-replays.mjs +1416 -0
- package/scripts/upgrade-guardian.sh +64 -29
|
@@ -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
|
|
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 +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(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
|
+
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 +1 @@
|
|
|
1
|
-
import{log as l}from"../../core/log/index.js";class c{controlURL="";token="";isConfigured(){return!!(this.controlURL&&this.token)}configure(
|
|
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,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
|
|
2
|
-
`),"utf8"),{expectPath:a,pidPath:i}}function h(
|
|
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 +1 @@
|
|
|
1
|
-
const e="claude/base",t="grix-claude",i={sessionControl:"session_control",setMode:"set_mode",setModel:"set_model",interactionReply:"claude_interaction_reply",fileList:"file_list",createFolder:"create_folder",getSessionUsage:"get_session_usage",getRateLimits:"get_rate_limits",threadCompact:"thread_compact",getAgentGlobalConfig:"get_agent_global_config",auditGetManifest:"audit_get_manifest",auditListSpans:"audit_list_spans",auditListToolCalls:"audit_list_tool_calls",auditGetContentChunk:"audit_get_content_chunk"},o={interactionRequestCreate:"claude_interaction_request_create"},s={permission:"permission",elicitation:"elicitation"},n={decision:"decision",action:"action",text:"text",map:"map"},_={open:"open",status:"status",where:"where",stop:"stop",restart:"restart",setMode:"set_mode",setModel:"set_model",listOptions:"list_options",listSessions:"list_sessions",syncHistory:"sync_history",approve:"approve",reject:"reject",exec:"exec"},r={approval:"approval",fullAuto:"full_auto"},a={responded:"responded",failed:"failed",canceled:"canceled"},d={ok:"ok",failed:"failed"},l={requestIDRequired:"interaction_request_id_required",requestNotFound:"interaction_request_not_found",requestNotPending:"interaction_request_not_pending",resolutionInvalid:"interaction_resolution_invalid",forwardFailed:"interaction_forward_failed"},c={cwdRequired:"session_cwd_required",invalidCwd:"session_invalid_cwd",invalidAgentSession:"invalid_agent_session",bindingMissing:"session_binding_missing",rebindForbidden:"session_rebind_forbidden",verbInvalid:"session_verb_invalid",runtimeError:"session_runtime_error",workerBusy:"session_worker_busy"},u={unsupportedLocalAction:"unsupported_local_action",localActionRouteMissing:"local_action_route_missing",modeInvalid:"set_mode_invalid"};export{e as ADAPTER_HINT,o as AGENT_INVOKE_ACTIONS,t as CHANNEL_NAME,a as EVENT_RESULT_STATUSES,s as INTERACTION_KINDS,l as INTERACTION_REPLY_ERROR_CODES,n as INTERACTION_RESOLUTION_TYPES,u as LOCAL_ACTION_ERROR_CODES,d as LOCAL_ACTION_RESULT_STATUSES,i as LOCAL_ACTION_TYPES,c as SESSION_CONTROL_ERROR_CODES,_ as SESSION_CONTROL_VERBS,r as SESSION_MODE_IDS};
|
|
1
|
+
const e="claude/base",t="grix-claude",i={sessionControl:"session_control",setMode:"set_mode",setModel:"set_model",setReasoningEffort:"set_reasoning_effort",interactionReply:"claude_interaction_reply",fileList:"file_list",createFolder:"create_folder",getSessionUsage:"get_session_usage",getRateLimits:"get_rate_limits",threadCompact:"thread_compact",getAgentGlobalConfig:"get_agent_global_config",auditGetManifest:"audit_get_manifest",auditListSpans:"audit_list_spans",auditListToolCalls:"audit_list_tool_calls",auditGetContentChunk:"audit_get_content_chunk"},o={interactionRequestCreate:"claude_interaction_request_create"},s={permission:"permission",elicitation:"elicitation"},n={decision:"decision",action:"action",text:"text",map:"map"},_={open:"open",status:"status",where:"where",stop:"stop",restart:"restart",setMode:"set_mode",setModel:"set_model",listOptions:"list_options",listSessions:"list_sessions",syncHistory:"sync_history",approve:"approve",reject:"reject",exec:"exec"},r={approval:"approval",fullAuto:"full_auto"},a={responded:"responded",failed:"failed",canceled:"canceled"},d={ok:"ok",failed:"failed"},l={requestIDRequired:"interaction_request_id_required",requestNotFound:"interaction_request_not_found",requestNotPending:"interaction_request_not_pending",resolutionInvalid:"interaction_resolution_invalid",forwardFailed:"interaction_forward_failed"},c={cwdRequired:"session_cwd_required",invalidCwd:"session_invalid_cwd",invalidAgentSession:"invalid_agent_session",bindingMissing:"session_binding_missing",rebindForbidden:"session_rebind_forbidden",verbInvalid:"session_verb_invalid",runtimeError:"session_runtime_error",workerBusy:"session_worker_busy"},u={unsupportedLocalAction:"unsupported_local_action",localActionRouteMissing:"local_action_route_missing",modeInvalid:"set_mode_invalid",effortInvalid:"set_reasoning_effort_invalid"};export{e as ADAPTER_HINT,o as AGENT_INVOKE_ACTIONS,t as CHANNEL_NAME,a as EVENT_RESULT_STATUSES,s as INTERACTION_KINDS,l as INTERACTION_REPLY_ERROR_CODES,n as INTERACTION_RESOLUTION_TYPES,u as LOCAL_ACTION_ERROR_CODES,d as LOCAL_ACTION_RESULT_STATUSES,i as LOCAL_ACTION_TYPES,c as SESSION_CONTROL_ERROR_CODES,_ as SESSION_CONTROL_VERBS,r as SESSION_MODE_IDS};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
class m{defaultTimeoutMs;onTimeout;timers=new Map;constructor(
|
|
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};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function m(r,n="unknown session error"){if(r==null)return n;if(typeof r=="string"){const t=r.trim();if(!t)return n;if(t.startsWith("{")||t.startsWith("["))try{return m(JSON.parse(t),t)}catch{return t}return t}if(typeof r!="object")return String(r);const e=r;if(typeof e.message=="string"&&e.message.trim())return e.message.trim();const s=e.data;if(s&&typeof s=="object"){const t=s;if(typeof t.message=="string"&&t.message.trim())return t.message.trim();const o=f(t.responseBody);if(o)return o}const i=f(e.responseBody);if(i)return i;if(typeof e.type=="string"&&e.type.trim())return e.type.trim();if(typeof e.name=="string"&&e.name.trim())return e.name.trim();try{const t=JSON.stringify(r);return!t||t==="{}"?n:t.length>240?`${t.slice(0,240)}\u2026`:t}catch{return n}}function f(r){if(!(typeof r!="string"||!r.trim()))try{const n=JSON.parse(r);if(!n||typeof n!="object")return;const e=n.error;if(e&&typeof e=="object"){const s=e.message;if(typeof s=="string"&&s.trim())return s.trim()}if(typeof n.message=="string"&&n.message.trim())return n.message.trim()}catch{}}export{m as formatOpenCodeErrorMessage};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import{EventEmitter as _}from"node:events";import{stat as E}from"node:fs/promises";import{existsSync as
|
|
2
|
-
`,"utf8"),
|
|
3
|
-
`)){const T=I.match(/opencode server listening on (https?:\/\/[^\s]+)/);if(T){clearTimeout(v),l=!0,this.alive=!0,i(T[1]);return}}}),this.process.stderr?.on("data",h=>{const I=h.toString().trim();I&&o.info("opencode-adapter",`[stderr] ${I}`)}),this.process.on("error",h=>{o.error("opencode-adapter",`Spawn error: ${h.message}`),clearTimeout(v),this.alive=!1,this.transport.disconnect(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed",`Spawn error: ${h.message}`),this.clearRun()),l?this.stopped||this.emit("exit",1):(l=!0,n(h))}),this.process.on("exit",h=>{o.info("opencode-adapter",`Process exited (code=${h})`),clearTimeout(v),this.alive=!1,this.transport.disconnect(),this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed",`Process exited (code=${h})`),this.clearRun()),l?this.stopped||this.emit("exit",h??1):(l=!0,n(new Error(`opencode serve exited with code ${h}`)))})})}async ensureOcSession(e){const t=this.sessions.get(e),s=this.getSessionCwd(e);if(await this.transport.useDirectory(s),t?.ocSessionId)try{return await this.transport.getSession(t.ocSessionId,s),t.ocSessionId}catch{o.warn("opencode-adapter",`OC session ${t.ocSessionId} gone, creating new`),t.ocSessionId=""}const i=this.options.bindingStore?.getAcpSessionId(e);if(i)try{return await this.transport.getSession(i,s),this.sessions.set(e,{ocSessionId:i,cwd:s}),o.info("opencode-adapter",`Resumed OC session ${i} for aibot=${e}`),await this.applySessionPermission(e,i,s),i}catch{o.warn("opencode-adapter",`Persisted OC session ${i} gone, creating new`)}const n=await this.transport.createSession({title:`grix-${e.slice(-8)}`},s);return this.sessions.set(e,{ocSessionId:n.id,cwd:s}),this.options.bindingStore?.setAcpSessionId(e,n.id),o.info("opencode-adapter",`Created OC session ${n.id} for aibot=${e}`),await this.applySessionPermission(e,n.id,s),n.id}async sendOcPrompt(e,t){const s=this.getSessionCwd(e),i=await this.ensureOcSession(e);this.activeRun&&(this.activeRun.ocSessionId=i,this.activeRun.cwd=s);const n=this.resolveModelId(e),r=n.indexOf("/"),a=r>0&&r<n.length-1?{providerID:n.slice(0,r),modelID:n.slice(r+1)}:void 0;await this.transport.sendPromptAsync(i,{parts:[{type:"text",text:t}],...a?{model:a}:{}},s)}handleSseEvent(e){if(!this.stopped){if(this.lastSseAt=Date.now(),this.updateToolInFlight(e),this.resetIdleTimer(),this.activeRun){const t=this.auditCaptures.get(this.activeRun.eventId);t&&t.push({sequence:t.length,observedAt:new Date().toISOString(),event:structuredClone(e)})}switch(e.type){case"message.part.updated":{if(!this.activeRun)break;const t=e.part,s=e.delta;if(t.type==="text"){const i=s||t.text;i&&this.acceptAssistantPartText(t.messageID,"text",i)}else t.type==="reasoning"?s&&this.acceptAssistantPartText(t.messageID,"reasoning",s):t.type==="tool"&&this.handleToolPartUpdate(t,s);break}case"message.updated":{if(!this.activeRun)break;const t=e.info;if(this.rememberMessageRole(t),t.role==="assistant"){const s=t;this.callbacks.sendUpdateBindingCard(this.activeRun.sessionId,"composing",this.activeRun.cwd,{...this.buildToolbarMeta(this.activeRun.sessionId),model_provider:s.providerID,model_id:s.modelID}),s.error&&o.warn("opencode-adapter",`Message error: ${JSON.stringify(s.error)}`);const i=this.activeRun.ocSessionId;i&&s.sessionID===i&&(s.error?(this.flushTextBuffer(),this.finishRun("failed",s.error.message||s.error.type)):s.time?.completed&&s.finish!=="tool-calls"&&(this.flushTextBuffer(),this.finishRun("responded")))}break}case"session.idle":{if(!this.activeRun)break;e.sessionID===this.activeRun.ocSessionId&&(this.flushTextBuffer(),this.finishRun("responded"));break}case"session.status":{if(!this.activeRun)break;e.sessionID===this.activeRun.ocSessionId&&e.status.type==="idle"&&(this.flushTextBuffer(),this.finishRun("responded"));break}case"session.error":{if(!this.activeRun)break;const t=e.error,s=t?typeof t=="object"&&"message"in t?t.message:JSON.stringify(t):"unknown session error";o.error("opencode-adapter",`Session error: ${s}`),this.flushTextBuffer(),this.finishRun("failed",s);break}case"permission.updated":{this.handlePermission(e.permission);break}case"permission.asked":{this.handlePermissionAsked(e);break}case"question.asked":{this.handleQuestionAsked(e);break}case"permission.replied":case"question.replied":case"question.rejected":o.debug("opencode-adapter",`Interaction settled: ${e.type}`);break;case"session.created":case"session.updated":case"session.deleted":case"session.compacted":case"session.diff":case"file.edited":case"server.connected":break;case"server.instance.disposed":{o.warn("opencode-adapter",`Server instance disposed: ${e.directory}`),this.alive=!1,this.transport.disconnect(),this.activeRun&&(this.flushTextBuffer(),this.callbacks.sendEventResult(this.activeRun.eventId,"failed","server instance disposed"),this.clearRun()),this.stopped||this.emit("exit",-1);break}default:o.debug("opencode-adapter",`Unhandled SSE event type: ${e.type}`);break}}}updateToolInFlight(e){if(e.type!=="message.part.updated")return;const t=e.part;if(t?.type!=="tool"||!t.callID)return;const s=t.state?.status;s==="pending"||s==="running"?this.toolCallsInFlight.add(t.callID):(s==="completed"||s==="error")&&this.toolCallsInFlight.delete(t.callID)}rememberMessageRole(e){const t=typeof e.id=="string"?e.id.trim():"";t&&(e.role!=="user"&&e.role!=="assistant"||(this.messageRoles.set(t,e.role),this.flushOrDropPendingParts(t,e.role)))}acceptAssistantPartText(e,t,s){if(!this.activeRun||!s)return;const i=typeof e=="string"?e.trim():"";if(!i)return;const n=this.messageRoles.get(i);if(n==="user")return;if(n==="assistant"){this.emitAssistantPartChunk(t,s);return}const r=this.pendingPartChunks.get(i)??[];r.push({kind:t,value:s}),this.pendingPartChunks.set(i,r)}flushOrDropPendingParts(e,t){const s=this.pendingPartChunks.get(e);if(this.pendingPartChunks.delete(e),!(!s||s.length===0)&&t==="assistant")for(const i of s)this.emitAssistantPartChunk(i.kind,i.value)}emitAssistantPartChunk(e,t){if(!(!this.activeRun||!t)){if(e==="reasoning"){this.callbacks.sendThinking(this.activeRun.eventId,this.activeRun.sessionId,t);return}this.appendText(t)}}clearMessageRoleState(){this.messageRoles.clear(),this.pendingPartChunks.clear()}handleToolPartUpdate(e,t){if(!this.activeRun)return;const s=e.state;switch(s.status){case"pending":case"running":{this.sendToolUseOnce(e,s.input);break}case"completed":{this.sendToolUseOnce(e,s.input);break}case"error":{this.sendToolUseOnce(e,s.input),this.callbacks.sendToolResult(this.activeRun.eventId,this.activeRun.sessionId,e.tool,"(failed)");break}}}sendToolUseOnce(e,t){!this.activeRun||this.toolCardsSent.has(e.callID)||(this.flushTextBuffer(),this.toolCardsSent.add(e.callID),this.callbacks.sendToolUse(this.activeRun.eventId,this.activeRun.sessionId,e.tool,z(t??{})))}onPermissionTimeout(e){const t=this.pendingPermissions.get(e);t&&(this.pendingPermissions.delete(e),o.error("opencode-adapter",`Permission approval timeout: ${e}`),this.transport.respondPermission(t.ocSessionId,e,"reject",t.cwd).catch(()=>{}),this.activeRun&&this.activeRun.eventId===t.eventId&&(this.flushTextBuffer(),this.finishRun("failed","permission approval timeout")))}trackPendingPermission(e){if(!this.activeRun)return;this.stopIdleTimer();const t=setTimeout(()=>this.onPermissionTimeout(e),R);this.pendingPermissions.set(e,{permissionId:e,eventId:this.activeRun.eventId,ocSessionId:this.activeRun.ocSessionId,cwd:this.activeRun.cwd,timer:t})}handlePermissionAsked(e){if(!this.activeRun){this.transport.respondPermission(e.sessionID,e.id,"reject").catch(r=>{o.warn("opencode-adapter",`Orphan permission reject failed: ${r}`)});return}const{eventId:t,sessionId:s,ocSessionId:i}=this.activeRun;if(this.resolveModeId(s)===m.fullAuto){this.transport.respondPermission(i,e.id,"always",this.activeRun.cwd).catch(r=>{o.warn("opencode-adapter",`Auto-approve failed: ${r}`)});return}this.trackPendingPermission(e.id);const n=e.patterns.length>0?`${e.permission}: ${e.patterns.join(", ")}`:e.permission;this.callbacks.sendPermissionCard?this.callbacks.sendPermissionCard({eventId:t,sessionId:s,approvalId:e.id,toolName:e.permission,toolTitle:n,toolInput:JSON.stringify(e.metadata??{})}):this.callbacks.sendToolUse(t,s,e.permission,JSON.stringify(e.metadata??{}))}handlePermission(e){if(!this.activeRun)return;const{eventId:t,sessionId:s,ocSessionId:i}=this.activeRun;if(this.resolveModeId(s)===m.fullAuto){this.transport.respondPermission(i,e.id,"always",this.activeRun.cwd).catch(n=>{o.warn("opencode-adapter",`Auto-approve failed: ${n}`)});return}if(this.trackPendingPermission(e.id),this.callbacks.sendPermissionCard)this.callbacks.sendPermissionCard({eventId:t,sessionId:s,approvalId:e.id,toolName:e.type,toolTitle:e.title||e.type,toolInput:JSON.stringify(e.metadata??{})});else{const n=JSON.stringify(e.metadata??{});this.callbacks.sendToolUse(t,s,e.type,n)}}handlePermissionAction(e){if(!this.activeRun)return{handled:!1,kind:""};const t=e.params??{},s=String(t.approval_id??t.approval_command_id??t.approvalId??t.tool_call_id??"");let i=s?this.pendingPermissions.get(s):void 0;if(!i){for(const a of this.pendingPermissions.values())if(a.eventId===this.activeRun.eventId){i=a;break}}if(!i)return this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"unknown_or_expired_approval_id","That approval request is no longer pending."),{handled:!0,kind:"permission"};const n=e.action_type==="exec_approve"||e.action_type==="permission_approve",r=n?"once":"reject";return clearTimeout(i.timer),this.pendingPermissions.delete(i.permissionId),this.transport.respondPermission(i.ocSessionId,i.permissionId,r,i.cwd).then(()=>{o.info("opencode-adapter",`Permission ${r}: ${i.permissionId}`),this.resetIdleTimer()}).catch(a=>{o.warn("opencode-adapter",`Permission response failed: ${a}`),this.resetIdleTimer()}),this.callbacks.sendLocalActionResult(e.action_id,"ok",{approval_id:i.permissionId,decision:n?"approve":"reject"}),{handled:!0,kind:"permission"}}onQuestionTimeout(e){const t=this.pendingQuestions.get(e);t&&(this.pendingQuestions.delete(e),o.error("opencode-adapter",`Question answer timeout: ${e}`),this.transport.rejectQuestion(e,t.cwd).catch(()=>{}),this.activeRun&&this.activeRun.eventId===t.eventId&&(this.flushTextBuffer(),this.finishRun("failed","question answer timeout")))}handleQuestionAsked(e){if(!this.activeRun){this.transport.rejectQuestion(e.id).catch(n=>{o.warn("opencode-adapter",`Orphan question reject failed: ${n}`)});return}const{eventId:t,sessionId:s}=this.activeRun;this.stopIdleTimer();const i=setTimeout(()=>this.onQuestionTimeout(e.id),R);if(this.pendingQuestions.set(e.id,{requestId:e.id,eventId:t,cwd:this.activeRun.cwd,questions:e.questions,timer:i}),this.callbacks.sendAgentQuestionCard)this.callbacks.sendAgentQuestionCard(t,s,{request_id:e.id,mode:"form",questions:e.questions.map(n=>({header:n.header,prompt:n.question,...n.options.length>0?{options:n.options.map(r=>r.label)}:{},...n.multiple!==void 0?{multi_select:n.multiple}:{}})),expires_at:Date.now()+R});else{const n=e.questions.map(r=>r.question).join(`
|
|
4
|
-
`);this.callbacks.sendRunError(t
|
|
1
|
+
import{EventEmitter as _}from"node:events";import{stat as E}from"node:fs/promises";import{existsSync as M,mkdirSync as $,readFileSync as O,writeFileSync as F}from"node:fs";import{join as R,resolve as D,dirname as B}from"node:path";import{homedir as L}from"node:os";import{fileURLToPath as N}from"node:url";import{resolveCommandPath as j,spawnCommand as Q,killProcessGroup as y,hasChildProcesses as U}from"../../core/runtime/spawn.js";import{InternalApiServer as q}from"../../core/mcp/internal-api-server.js";import{IdentityInjector as H}from"../shared/identity-injector.js";import{syncDefaultSkillsToDir as J}from"../../default-skills/index.js";import{buildSimpleProbeReport as W}from"../shared/probe-util.js";import{compactToolInputForWire as z}from"../shared/tool-wire-payload.js";import{normalizeOCProviderModels as G}from"./opencode-types.js";import{buildOpencodeConfigContent as V}from"./opencode-config.js";import{formatOpenCodeErrorMessage as k}from"./format-error.js";import{OpenCodeTransport as X}from"./opencode-transport.js";import{log as r}from"../../core/log/index.js";import{splitTextForAibotProtocol as A}from"../../core/protocol/index.js";class Y extends _{adapterSessionId;constructor(e){super(),this.adapterSessionId=e}emitError(e){if(this.listenerCount("error")===0){r.warn("opencode-adapter",`Prompt handle error (no listeners): ${e.message}`);return}this.emit("error",e)}async cancel(){}}const K=200,Z=2e3,ee=12e4,te=90*1e3,x=1800*1e3,P=3e4,S=600*1e3,se="claude_interaction_reply",ie="set_mode",ne="set_model",oe="127.0.0.1",re=0,ae=1e3,m={fullAuto:"full_auto",approval:"approval"},b=[{id:m.fullAuto,displayName:"Full Auto"},{id:m.approval,displayName:"Approval"}],de=[{permission:"*",pattern:"*",action:"allow"}],ce=[{permission:"doom_loop",pattern:"*",action:"ask"},{permission:"external_directory",pattern:"*",action:"ask"},{permission:"question",pattern:"*",action:"deny"},{permission:"plan_enter",pattern:"*",action:"deny"},{permission:"plan_exit",pattern:"*",action:"deny"},{permission:"repo_clone",pattern:"*",action:"deny"},{permission:"repo_overview",pattern:"*",action:"deny"},{permission:"read",pattern:"*.env",action:"ask"},{permission:"read",pattern:"*.env.*",action:"ask"},{permission:"read",pattern:"*.env.example",action:"allow"}];function w(v){const e=String(v??"").trim().toLowerCase();if(e==="full_auto"||e==="fullauto")return m.fullAuto;if(e==="approval"||e==="ask"||e==="autoedit"||e==="auto_edit")return m.approval}function C(v){try{return process.kill(v,0),!0}catch(e){return e.code==="EPERM"}}class Pe extends _{type="opencode";config;callbacks;options;identity;process=null;transport=new X;alive=!1;stopped=!1;internalApi=null;sessions=new Map;sessionModes=new Map;sessionModels=new Map;availableModels=[];modelContextLimits=new Map;contextTokensBySession=new Map;activeRun=null;completedEvents=new Set;clientMsgSeq=0;auditCaptures=new Map;completedAuditBoundaries=new Map;idleTimer=null;lastSseAt=0;livenessExtendStartAt;toolCallsInFlight=new Set;toolCardsSent=new Set;messageRoles=new Map;pendingPartChunks=new Map;pendingPermissions=new Map;pendingQuestions=new Map;permissionHandler=null;constructor(e,s,t){super(),this.config=e,this.callbacks=s,this.options=t??{},this.identity=new H("opencode-adapter",s.getAgentProfile)}onAgentProfileChanged(){this.identity.onProfileChanged()}async start(){await this.startInternalApiAndInjectMcp();const e=this.options.hostname??oe,s=this.options.port??re,t=await this.spawnAndWait(e,s),i=this.resolveCwd();await this.transport.connect(t,i),this.transport.on("event",n=>this.handleSseEvent(n));try{await this.refreshModels()}catch(n){r.warn("opencode-adapter",`Failed to fetch providers for toolbar models: ${n instanceof Error?n.message:String(n)}`)}r.info("opencode-adapter",`Ready (pid=${this.process?.pid}, url=${t})`)}async refreshModels(e){const s=await this.transport.listProviders(e),t=[],i=new Map;for(const n of s)for(const o of G(n.models)){t.push({id:`${n.id}/${o.id}`,displayName:o.name||o.id});const a=o.limit?.context;typeof a=="number"&&a>0&&i.set(`${n.id}/${o.id}`,a)}t.sort((n,o)=>{const a=n.displayName.toLowerCase().localeCompare(o.displayName.toLowerCase());return a!==0?a:n.id.localeCompare(o.id)}),this.availableModels=t,this.modelContextLimits=i,r.info("opencode-adapter",`Fetched ${t.length} models from ${s.length} providers (directory=${e??"current"})`)}resolveContextLimit(e,s){const t=this.modelContextLimits.get(`${e}/${s}`);if(t)return t;let i;for(const[n,o]of this.modelContextLimits)n.startsWith(`${e}/`)&&(i=i===void 0?o:Math.max(i,o));return i}buildContextWindowMeta(e,s,t,i){if(!i||i<=0)return;const n=this.resolveContextLimit(s,t);if(!n)return;const o=Math.min(100,i/n*100);return{context_window:{used:i,size:n,usedPercentage:o,remainingPercentage:100-o}}}async stop(){if(this.stopped=!0,this.alive=!1,this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.transport.disconnect(),this.internalApi&&(await this.internalApi.stop(),this.internalApi=null),this.process){const e=this.process;try{y(e,"SIGTERM")}catch{}const s=setTimeout(()=>{try{y(e,"SIGKILL")}catch{}},5e3);e.on("exit",()=>clearTimeout(s)),this.process=null}}isAlive(){return this.alive}async createSession(e){const s=e.cwd??this.resolveCwd();await this.transport.useDirectory(s);const t=await this.transport.createSession({title:`grix-${Date.now()}`},s);return this.sessions.set(t.id,{ocSessionId:t.id,cwd:s}),r.info("opencode-adapter",`Created OC session ${t.id} for cwd=${s}`),await this.applySessionPermission(t.id,t.id,s),t.id}async resumeSession(e,s){const t=this.sessions.get(e);if(t?.ocSessionId)try{await this.transport.useDirectory(t.cwd),await this.transport.getSession(t.ocSessionId,t.cwd),await this.applySessionPermission(e,t.ocSessionId,t.cwd)}catch{r.warn("opencode-adapter",`OC session ${t.ocSessionId} gone, will create new on next prompt`),t.ocSessionId=""}}async destroySession(e){const s=this.sessions.get(e),t=s?s.ocSessionId:e;if(t)try{await this.transport.deleteSession(t,s?.cwd)}catch{}this.sessions.delete(e),this.contextTokensBySession.delete(e),this.identity.forgetSession(e)}sendPrompt(e){const s=new Y(e.adapterSessionId);return this.sendOcPrompt(e.adapterSessionId,this.buildPromptText(e)).catch(t=>{s.emitError(t instanceof Error?t:new Error(String(t)))}),s}async cancel(e){if(this.activeRun)try{await this.transport.abortSession(this.activeRun.ocSessionId,this.activeRun.cwd)}catch{}}deliverInboundEvent(e){const{event_id:s,session_id:t,content:i}=e;if(this.completedEvents.has(s)){r.info("opencode-adapter",`Dropping duplicate event ${s}`),this.callbacks.sendEventAck(s,t),this.callbacks.sendEventResult(s,"responded");return}if(!this.alive){r.warn("opencode-adapter",`Dropping event ${s}: process not alive`),this.callbacks.sendEventAck(s,t),this.callbacks.sendEventResult(s,"failed","Agent process not running");return}this.activeRun&&this.activeRun.eventId!==s&&(r.info("opencode-adapter",`steer: ${this.activeRun.eventId} -> ${s}`),this.flushTextBuffer(),this.callbacks.sendEventResult(this.activeRun.eventId,"canceled","steered to new event"),this.clearRun()),r.info("opencode-adapter",`prompt: event=${s} session=${t}`),this.callbacks.sendEventAck(s,t),this.startRun(s,t,e.audit?.enabled===!0),this.startComposing(t),this.resetIdleTimer();const n=this.buildPromptTextFromEvent(e);this.sendOcPrompt(t,n).catch(o=>{r.error("opencode-adapter",`prompt_async failed: ${o}`),this.finishRun("failed",String(o))})}deliverStopEvent(e,s){this.activeRun&&this.activeRun.eventId===e&&(r.info("opencode-adapter",`stop: event=${e}`),this.rejectAllPendingInteractions(),this.transport.abortSession(this.activeRun.ocSessionId,this.activeRun.cwd).catch(()=>{}),this.flushTextBuffer(),this.finishRun("canceled","stopped by user"))}setPermissionHandler(e){this.permissionHandler=e}async ping(e){return this.transport.healthCheck()}getStatus(){return{alive:this.alive,busy:this.activeRun!==null,sessions:this.sessions.size}}getActiveEventIds(){return this.activeRun?[this.activeRun.eventId]:[]}takeAuditBoundary(e){const s=this.completedAuditBoundaries.get(e);return s&&this.completedAuditBoundaries.delete(e),s}clearActiveEventForShutdown(){this.stopIdleTimer(),this.stopTextFlush(),this.rejectAllPendingInteractions(),this.activeRun&&this.snapshotAuditBoundary(this.activeRun),this.activeRun=null}getMcpConfig(){if(!this.internalApi)return null;const e=D(N(import.meta.url),"../../../mcp/acp-mcp-server.js");return{name:"grix-connector-tools",command:process.execPath,args:[e,"--api-url",this.internalApi.url]}}async hasBackgroundWork(){const e=this.process?.pid;return e?U(e,[e]):!1}async probe(e){const s=this.getStatus();return W(this.config.command||"opencode",{alive:s.alive,busy:s.busy,started:!!this.process},e)}async startInternalApiAndInjectMcp(){try{this.internalApi=new q,this.internalApi.setInvokeHandler(async(f,d,h,c)=>this.callbacks.agentInvoke(f,d,c)),await this.internalApi.start(0),r.info("opencode-adapter",`Internal API started at ${this.internalApi.url}`);const e=this.getMcpConfig(),s=process.env.XDG_CONFIG_HOME||R(L(),".config"),t=R(s,"opencode","opencode.json");$(B(t),{recursive:!0});let i={};try{M(t)&&(i=JSON.parse(O(t,"utf8")))}catch{}const n=i.mcp&&typeof i.mcp=="object"?i.mcp:{};n[e.name]={type:"local",command:[e.command,...e.args??[]],enabled:!0},i.mcp=n,F(t,`${JSON.stringify(i,null,2)}
|
|
2
|
+
`,"utf8"),r.info("opencode-adapter",`MCP config injected into ${t}`);const o=R(s,"opencode","skills"),a=J(o);a.length>0&&r.info("opencode-adapter",`Synced connector skills to ${o}: [${a.join(", ")}]`)}catch(e){r.warn("opencode-adapter",`Failed to inject MCP tools (non-fatal): ${e instanceof Error?e.message:String(e)}`)}}async handleLocalAction(e){const{action_type:s}=e;if(s==="exec_approve"||s==="exec_reject"||s==="permission_approve"||s==="permission_reject")return this.handlePermissionAction(e);if(s===ie)return this.handleSetModeAction(e);if(s===ne)return this.handleSetModelAction(e);if(s===se){const t=e.params??{};if(String(t.kind??"")==="permission"){const i=t.resolution??{},n=String(i.value??"");return this.handlePermissionAction({...e,action_type:n==="allow"?"exec_approve":"exec_reject",params:{...t,approval_command_id:t.request_id}})}return this.handleQuestionReplyAction(e)}return{handled:!1,kind:""}}handleQuestionReplyEvent(e){let s;if(e.action)s={type:"action",value:e.action};else if(e.response?.type==="single")s={type:"text",value:e.response.value};else if(e.response?.type==="map")s={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:s})}async bindSession(e,s){if(r.info("opencode-adapter",`bindSession: ${e} \u2192 ${s}`),!this.sessions.has(e))this.sessions.set(e,{ocSessionId:"",cwd:s});else{const t=this.sessions.get(e);t.cwd=s}try{await this.refreshModels(s)}catch(t){r.warn("opencode-adapter",`Failed to refresh models on bind: ${t instanceof Error?t.message:String(t)}`)}this.callbacks.sendUpdateBindingCard(e,"ready",s,this.buildToolbarMeta(e))}resolveModeId(e){if(e){const t=this.sessionModes.get(e);if(t)return t}const s=w(this.options.permissionPolicy);if(s)return s;if(e){const t=w(this.options.bindingStore?.get(e)?.modeId);if(t)return t}return m.fullAuto}buildModeToolbarMeta(e){const s=this.resolveModeId(e);return{mode_id:s,currentModeId:s,available_modes:b.map(t=>({...t}))}}async applySessionPermission(e,s,t){const i=this.resolveModeId(e),n=i===m.fullAuto?de:ce;try{await this.transport.updateSessionPermission(s,n,t),r.info("opencode-adapter",`Applied session permission ruleset (${i}) to ${s}`)}catch(o){r.warn("opencode-adapter",`Failed to apply session permission ruleset: ${o instanceof Error?o.message:String(o)}`)}}async handleSetModeAction(e){const s=e.params??{},t=String(s.session_id??this.options.aibotSessionId??"").trim(),i=w(s.mode_id);if(!i||!t)return this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"invalid_params","mode_id must be full_auto or approval and session_id is required"),{handled:!0,kind:"set_mode"};this.sessionModes.set(t,i);const n=this.sessions.get(t);n?.ocSessionId&&await this.applySessionPermission(t,n.ocSessionId,n.cwd);const o=n?.cwd??this.getSessionCwd(t);return this.callbacks.sendUpdateBindingCard(t,"ready",o,this.buildToolbarMeta(t)),this.callbacks.sendLocalActionResult(e.action_id,"ok",{outcome:"mode_set",session_context:{mode_id:i,modeId:i},mode_id:i,available_modes:b.map(a=>({...a}))}),{handled:!0,kind:"set_mode"}}resolveModelId(e){if(e){const t=this.sessionModels.get(e);if(t)return t}const s=String(this.options.model??"").trim();if(s)return s;if(e){const t=String(this.options.bindingStore?.get(e)?.modelId??"").trim();if(t)return t}return""}buildModelToolbarMeta(e){const s=this.resolveModelId(e);return{model_id:s,currentModelId:s,available_models:this.availableModels.map(t=>({...t}))}}buildToolbarMeta(e){return{...this.buildModelToolbarMeta(e),...this.buildModeToolbarMeta(e)}}handleSetModelAction(e){const s=e.params??{},t=String(s.session_id??this.options.aibotSessionId??"").trim(),i=String(s.model_id??"").trim();if(!i||!t)return this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"invalid_params","model_id and session_id are required"),{handled:!0,kind:"set_model"};this.sessionModels.set(t,i);const n=this.sessions.get(t)?.cwd??this.getSessionCwd(t);return this.callbacks.sendUpdateBindingCard(t,"ready",n,this.buildToolbarMeta(t)),this.callbacks.sendLocalActionResult(e.action_id,"ok",{outcome:"model_set",session_context:{model_id:i,modelId:i},model_id:i,available_models:this.availableModels.map(o=>({...o}))}),{handled:!0,kind:"set_model"}}async spawnAndWait(e,s){const t=this.resolveCwd();try{if(!(await E(t)).isDirectory())throw new Error(`Bound path is not a directory: ${t}`)}catch(i){throw String(i?.code??"")==="ENOENT"?new Error(`Bound directory does not exist: ${t}. Please rebind with /grix open <valid-directory>.`):i}return new Promise((i,n)=>{const o=this.config.command||"opencode",f=[...this.config.args??["serve"],`--hostname=${e}`,`--port=${s}`],d={...process.env,...this.config.env},h=V({model:this.options.model,permissionPolicy:this.options.permissionPolicy,provider:this.options.provider});h&&(d.OPENCODE_CONFIG_CONTENT=JSON.stringify(h));const c=j(o,typeof d.PATH=="string"?d.PATH:void 0);r.info("opencode-adapter",`Spawning: ${c} ${f.join(" ")} (cwd=${t})`),this.process=Q(c,f,{env:d,cwd:t}).process;let u="",p=!1;const I=setTimeout(()=>{p||(p=!0,n(new Error(`opencode serve did not start after ${P/1e3}s`)))},P);this.process.stdout?.on("data",l=>{if(u+=l.toString(),!p)for(const g of u.split(`
|
|
3
|
+
`)){const T=g.match(/opencode server listening on (https?:\/\/[^\s]+)/);if(T){clearTimeout(I),p=!0,this.alive=!0,i(T[1]);return}}}),this.process.stderr?.on("data",l=>{const g=l.toString().trim();g&&r.info("opencode-adapter",`[stderr] ${g}`)}),this.process.on("error",l=>{r.error("opencode-adapter",`Spawn error: ${l.message}`),clearTimeout(I),this.alive=!1,this.transport.disconnect(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed",`Spawn error: ${l.message}`),this.clearRun()),p?this.stopped||this.emit("exit",1):(p=!0,n(l))}),this.process.on("exit",l=>{r.info("opencode-adapter",`Process exited (code=${l})`),clearTimeout(I),this.alive=!1,this.transport.disconnect(),this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed",`Process exited (code=${l})`),this.clearRun()),p?this.stopped||this.emit("exit",l??1):(p=!0,n(new Error(`opencode serve exited with code ${l}`)))})})}async ensureOcSession(e){const s=this.sessions.get(e),t=this.getSessionCwd(e);if(await this.transport.useDirectory(t),s?.ocSessionId)try{return await this.transport.getSession(s.ocSessionId,t),s.ocSessionId}catch{r.warn("opencode-adapter",`OC session ${s.ocSessionId} gone, creating new`),s.ocSessionId=""}const i=this.options.bindingStore?.getAcpSessionId(e);if(i)try{return await this.transport.getSession(i,t),this.sessions.set(e,{ocSessionId:i,cwd:t}),r.info("opencode-adapter",`Resumed OC session ${i} for aibot=${e}`),await this.applySessionPermission(e,i,t),i}catch{r.warn("opencode-adapter",`Persisted OC session ${i} gone, creating new`)}const n=await this.transport.createSession({title:`grix-${e.slice(-8)}`},t);return this.sessions.set(e,{ocSessionId:n.id,cwd:t}),this.options.bindingStore?.setAcpSessionId(e,n.id),r.info("opencode-adapter",`Created OC session ${n.id} for aibot=${e}`),await this.applySessionPermission(e,n.id,t),n.id}async sendOcPrompt(e,s){const t=this.getSessionCwd(e),i=await this.ensureOcSession(e);this.activeRun&&(this.activeRun.ocSessionId=i,this.activeRun.cwd=t);const n=this.resolveModelId(e),o=n.indexOf("/"),a=o>0&&o<n.length-1?{providerID:n.slice(0,o),modelID:n.slice(o+1)}:void 0;await this.transport.sendPromptAsync(i,{parts:[{type:"text",text:s}],...a?{model:a}:{}},t)}handleSseEvent(e){if(!this.stopped){if(this.updateToolInFlight(e),e.type!=="server.heartbeat"&&(this.lastSseAt=Date.now(),this.resetIdleTimer()),this.activeRun){const s=this.auditCaptures.get(this.activeRun.eventId);s&&s.push({sequence:s.length,observedAt:new Date().toISOString(),event:structuredClone(e)})}switch(e.type){case"message.part.updated":{if(!this.activeRun)break;const s=e.part,t=e.delta;if(s.type==="text"){const i=t||s.text;i&&this.acceptAssistantPartText(s.messageID,"text",i)}else s.type==="reasoning"?t&&this.acceptAssistantPartText(s.messageID,"reasoning",t):s.type==="tool"&&this.handleToolPartUpdate(s,t);break}case"message.updated":{if(!this.activeRun)break;const s=e.info;if(this.rememberMessageRole(s),s.role==="assistant"){const t=s;typeof t.tokens?.input=="number"&&this.contextTokensBySession.set(this.activeRun.sessionId,t.tokens.input);const i=this.buildContextWindowMeta(this.activeRun.sessionId,t.providerID,t.modelID,t.tokens?.input);this.callbacks.sendUpdateBindingCard(this.activeRun.sessionId,"composing",this.activeRun.cwd,{...this.buildToolbarMeta(this.activeRun.sessionId),model_provider:t.providerID,model_id:t.modelID,...i??{}}),t.error&&r.warn("opencode-adapter",`Message error: ${JSON.stringify(t.error)}`);const n=this.activeRun.ocSessionId;n&&t.sessionID===n&&(t.error?(this.flushTextBuffer(),this.finishRun("failed",k(t.error,t.error.type||"unknown error"))):t.time?.completed&&t.finish!=="tool-calls"&&(this.flushTextBuffer(),this.finishRun("responded")))}break}case"session.idle":{if(!this.activeRun)break;e.sessionID===this.activeRun.ocSessionId&&(this.flushTextBuffer(),this.finishRun("responded"));break}case"session.status":{if(!this.activeRun)break;e.sessionID===this.activeRun.ocSessionId&&e.status.type==="idle"&&(this.flushTextBuffer(),this.finishRun("responded"));break}case"session.error":{if(!this.activeRun)break;const s=k(e.error);r.error("opencode-adapter",`Session error: ${s}`),this.flushTextBuffer(),this.finishRun("failed",s);break}case"permission.updated":{this.handlePermission(e.permission);break}case"permission.asked":{this.handlePermissionAsked(e);break}case"question.asked":{this.handleQuestionAsked(e);break}case"permission.replied":case"question.replied":case"question.rejected":r.debug("opencode-adapter",`Interaction settled: ${e.type}`);break;case"session.created":case"session.updated":case"session.deleted":case"session.compacted":case"session.diff":case"file.edited":case"server.connected":case"server.heartbeat":break;case"server.instance.disposed":{r.warn("opencode-adapter",`Server instance disposed: ${e.directory}`),this.alive=!1,this.transport.disconnect(),this.activeRun&&(this.flushTextBuffer(),this.callbacks.sendEventResult(this.activeRun.eventId,"failed","server instance disposed"),this.clearRun()),this.stopped||this.emit("exit",-1);break}default:r.debug("opencode-adapter",`Unhandled SSE event type: ${e.type}`);break}}}updateToolInFlight(e){if(e.type!=="message.part.updated")return;const s=e.part;if(s?.type!=="tool"||!s.callID)return;const t=s.state?.status;t==="pending"||t==="running"?this.toolCallsInFlight.add(s.callID):(t==="completed"||t==="error")&&this.toolCallsInFlight.delete(s.callID)}rememberMessageRole(e){const s=typeof e.id=="string"?e.id.trim():"";s&&(e.role!=="user"&&e.role!=="assistant"||(this.messageRoles.set(s,e.role),this.flushOrDropPendingParts(s,e.role)))}acceptAssistantPartText(e,s,t){if(!this.activeRun||!t)return;const i=typeof e=="string"?e.trim():"";if(!i)return;const n=this.messageRoles.get(i);if(n==="user")return;if(n==="assistant"){this.emitAssistantPartChunk(s,t);return}const o=this.pendingPartChunks.get(i)??[];o.push({kind:s,value:t}),this.pendingPartChunks.set(i,o)}flushOrDropPendingParts(e,s){const t=this.pendingPartChunks.get(e);if(this.pendingPartChunks.delete(e),!(!t||t.length===0)&&s==="assistant")for(const i of t)this.emitAssistantPartChunk(i.kind,i.value)}emitAssistantPartChunk(e,s){if(!(!this.activeRun||!s)){if(e==="reasoning"){this.callbacks.sendThinking(this.activeRun.eventId,this.activeRun.sessionId,s);return}this.appendText(s)}}clearMessageRoleState(){this.messageRoles.clear(),this.pendingPartChunks.clear()}handleToolPartUpdate(e,s){if(!this.activeRun)return;const t=e.state;switch(t.status){case"pending":case"running":{this.sendToolUseOnce(e,t.input);break}case"completed":{this.sendToolUseOnce(e,t.input);break}case"error":{this.sendToolUseOnce(e,t.input),this.callbacks.sendToolResult(this.activeRun.eventId,this.activeRun.sessionId,e.tool,"(failed)");break}}}sendToolUseOnce(e,s){!this.activeRun||this.toolCardsSent.has(e.callID)||(this.flushTextBuffer(),this.toolCardsSent.add(e.callID),this.callbacks.sendToolUse(this.activeRun.eventId,this.activeRun.sessionId,e.tool,z(s??{})))}onPermissionTimeout(e){const s=this.pendingPermissions.get(e);s&&(this.pendingPermissions.delete(e),r.error("opencode-adapter",`Permission approval timeout: ${e}`),this.transport.respondPermission(s.ocSessionId,e,"reject",s.cwd).catch(()=>{}),this.activeRun&&this.activeRun.eventId===s.eventId&&(this.flushTextBuffer(),this.finishRun("failed","permission approval timeout")))}trackPendingPermission(e){if(!this.activeRun)return;this.stopIdleTimer();const s=setTimeout(()=>this.onPermissionTimeout(e),S);this.pendingPermissions.set(e,{permissionId:e,eventId:this.activeRun.eventId,ocSessionId:this.activeRun.ocSessionId,cwd:this.activeRun.cwd,timer:s})}handlePermissionAsked(e){if(!this.activeRun){this.transport.respondPermission(e.sessionID,e.id,"reject").catch(o=>{r.warn("opencode-adapter",`Orphan permission reject failed: ${o}`)});return}const{eventId:s,sessionId:t,ocSessionId:i}=this.activeRun;if(this.resolveModeId(t)===m.fullAuto){this.transport.respondPermission(i,e.id,"always",this.activeRun.cwd).catch(o=>{r.warn("opencode-adapter",`Auto-approve failed: ${o}`)});return}this.trackPendingPermission(e.id);const n=e.patterns.length>0?`${e.permission}: ${e.patterns.join(", ")}`:e.permission;this.callbacks.sendPermissionCard?this.callbacks.sendPermissionCard({eventId:s,sessionId:t,approvalId:e.id,toolName:e.permission,toolTitle:n,toolInput:JSON.stringify(e.metadata??{})}):this.callbacks.sendToolUse(s,t,e.permission,JSON.stringify(e.metadata??{}))}handlePermission(e){if(!this.activeRun)return;const{eventId:s,sessionId:t,ocSessionId:i}=this.activeRun;if(this.resolveModeId(t)===m.fullAuto){this.transport.respondPermission(i,e.id,"always",this.activeRun.cwd).catch(n=>{r.warn("opencode-adapter",`Auto-approve failed: ${n}`)});return}if(this.trackPendingPermission(e.id),this.callbacks.sendPermissionCard)this.callbacks.sendPermissionCard({eventId:s,sessionId:t,approvalId:e.id,toolName:e.type,toolTitle:e.title||e.type,toolInput:JSON.stringify(e.metadata??{})});else{const n=JSON.stringify(e.metadata??{});this.callbacks.sendToolUse(s,t,e.type,n)}}handlePermissionAction(e){if(!this.activeRun)return{handled:!1,kind:""};const s=e.params??{},t=String(s.approval_id??s.approval_command_id??s.approvalId??s.tool_call_id??"");let i=t?this.pendingPermissions.get(t):void 0;if(!i){for(const a of this.pendingPermissions.values())if(a.eventId===this.activeRun.eventId){i=a;break}}if(!i)return this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"unknown_or_expired_approval_id","That approval request is no longer pending."),{handled:!0,kind:"permission"};const n=e.action_type==="exec_approve"||e.action_type==="permission_approve",o=n?"once":"reject";return clearTimeout(i.timer),this.pendingPermissions.delete(i.permissionId),this.transport.respondPermission(i.ocSessionId,i.permissionId,o,i.cwd).then(()=>{r.info("opencode-adapter",`Permission ${o}: ${i.permissionId}`),this.resetIdleTimer()}).catch(a=>{r.warn("opencode-adapter",`Permission response failed: ${a}`),this.resetIdleTimer()}),this.callbacks.sendLocalActionResult(e.action_id,"ok",{approval_id:i.permissionId,decision:n?"approve":"reject"}),{handled:!0,kind:"permission"}}onQuestionTimeout(e){const s=this.pendingQuestions.get(e);s&&(this.pendingQuestions.delete(e),r.error("opencode-adapter",`Question answer timeout: ${e}`),this.transport.rejectQuestion(e,s.cwd).catch(()=>{}),this.activeRun&&this.activeRun.eventId===s.eventId&&(this.flushTextBuffer(),this.finishRun("failed","question answer timeout")))}handleQuestionAsked(e){if(!this.activeRun){this.transport.rejectQuestion(e.id).catch(n=>{r.warn("opencode-adapter",`Orphan question reject failed: ${n}`)});return}const{eventId:s,sessionId:t}=this.activeRun;this.stopIdleTimer();const i=setTimeout(()=>this.onQuestionTimeout(e.id),S);if(this.pendingQuestions.set(e.id,{requestId:e.id,eventId:s,cwd:this.activeRun.cwd,questions:e.questions,timer:i}),this.callbacks.sendAgentQuestionCard)this.callbacks.sendAgentQuestionCard(s,t,{request_id:e.id,mode:"form",questions:e.questions.map(n=>({header:n.header,prompt:n.question,...n.options.length>0?{options:n.options.map(o=>o.label)}:{},...n.multiple!==void 0?{multi_select:n.multiple}:{}})),expires_at:Date.now()+S});else{const n=e.questions.map(o=>o.question).join(`
|
|
4
|
+
`);this.callbacks.sendRunError(s,t,`Agent asks: ${n}`)}}handleQuestionReplyAction(e){const s=e.params??{},t=this.deliverQuestionReply(s);return t.delivered?this.callbacks.sendLocalActionResult(e.action_id,"ok",t.result):this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,t.errorCode,t.errorMsg),{handled:!0,kind:"question_reply"}}deliverQuestionReply(e){const s=String(e.request_id??""),t=s?this.pendingQuestions.get(s):void 0;if(!t)return{delivered:!1,errorCode:"interaction_request_not_pending",errorMsg:"The question is no longer pending; the reply was not delivered."};const i=e.resolution??{},n=String(i.type??""),o=(d,h)=>(clearTimeout(t.timer),this.pendingQuestions.delete(t.requestId),d().then(()=>this.resetIdleTimer()).catch(c=>{r.warn("opencode-adapter",`Question response failed: ${c}`),this.resetIdleTimer()}),{delivered:!0,result:h});if(n==="action"){const d=String(i.value??"");if(d==="cancel"||d==="decline")return o(()=>this.transport.rejectQuestion(t.requestId,t.cwd),{request_id:s,resolution:"cancel"})}let a=null;if(Array.isArray(e.answers))a=e.answers.map(d=>Array.isArray(d)?d.map(String):[String(d)]);else if(n==="text"){const d=String(i.value??"");d&&(a=t.questions.map((h,c)=>c===0?[d]:[]))}else if(n==="map"){const d=Array.isArray(i.entries)?i.entries:[];a=t.questions.map((h,c)=>{const u=d.find(p=>p.key===h.header||p.key===String(c+1));return u&&u.value?[u.value]:[]}),a.every(h=>h.length===0)&&d.length>0&&(a=t.questions.map((h,c)=>{const u=d[c]?.value;return u?[String(u)]:[]}))}if(!a||a.every(d=>d.length===0))return o(()=>this.transport.rejectQuestion(t.requestId,t.cwd),{request_id:s,resolution:"cancel"});const f=a;return o(()=>this.transport.replyQuestion(t.requestId,f,t.cwd),{request_id:s,resolution:"answer"})}rejectAllPendingInteractions(){for(const e of this.pendingPermissions.values())clearTimeout(e.timer),this.transport.respondPermission(e.ocSessionId,e.permissionId,"reject",e.cwd).catch(()=>{});this.pendingPermissions.clear();for(const e of this.pendingQuestions.values())clearTimeout(e.timer),this.transport.rejectQuestion(e.requestId,e.cwd).catch(()=>{});this.pendingQuestions.clear()}hasPendingInteractionForRun(e){for(const s of this.pendingPermissions.values())if(s.eventId===e)return!0;for(const s of this.pendingQuestions.values())if(s.eventId===e)return!0;return!1}startRun(e,s,t=!1){this.activeRun={eventId:e,sessionId:s,ocSessionId:"",cwd:this.getSessionCwd(s),chunkSeq:0,clientMsgId:`oc_${++this.clientMsgSeq}_${Date.now()}`,textBuffer:"",flushTimer:null},this.toolCardsSent.clear(),t&&this.auditCaptures.set(e,[]),this.lastSseAt=Date.now(),this.livenessExtendStartAt=void 0}finishRun(e,s){const t=this.activeRun;if(!t)return;if(this.completedEvents.add(t.eventId),this.completedEvents.size>ae){const a=[...this.completedEvents].slice(-500);this.completedEvents=new Set(a)}this.snapshotAuditBoundary(t),this.activeRun=null,this.rejectAllPendingInteractions(),this.toolCallsInFlight.clear(),this.toolCardsSent.clear(),this.livenessExtendStartAt=void 0,this.clearMessageRoleState(),this.stopComposing(),this.stopIdleTimer();const i=++t.chunkSeq,n=t.clientMsgId;e==="failed"&&s&&this.callbacks.sendRunError(t.eventId,t.sessionId,s);const o=()=>{this.callbacks.sendFinalStreamChunkReliable?this.callbacks.sendFinalStreamChunkReliable(t.eventId,t.sessionId,n).then(()=>{this.callbacks.sendEventResult(t.eventId,e,s)}).catch(a=>{const f=e==="responded"?"failed":e,d=e==="responded"?`final output delivery failed: ${a instanceof Error?a.message:String(a)}`:s;this.callbacks.sendEventResult(t.eventId,f,d)}):(this.callbacks.sendStreamChunk(t.eventId,t.sessionId,"",i,!0,n),this.callbacks.sendEventResult(t.eventId,e,s))};if(t.textBuffer){this.stopTextFlush();for(const a of A(t.textBuffer))t.chunkSeq++,this.callbacks.sendStreamChunk(t.eventId,t.sessionId,a,t.chunkSeq,!1,t.clientMsgId);t.textBuffer=""}o(),this.emit("eventDone",t.eventId)}clearRun(){this.activeRun?.flushTimer&&clearTimeout(this.activeRun.flushTimer);const e=this.activeRun?.eventId;this.activeRun&&this.snapshotAuditBoundary(this.activeRun),this.activeRun=null,this.rejectAllPendingInteractions(),this.toolCallsInFlight.clear(),this.toolCardsSent.clear(),this.livenessExtendStartAt=void 0,this.clearMessageRoleState(),this.stopIdleTimer(),e&&this.emit("eventDone",e)}snapshotAuditBoundary(e){const s=this.auditCaptures.get(e.eventId);s&&(this.completedAuditBoundaries.set(e.eventId,{adapterType:"opencode",providerSessionId:e.ocSessionId||void 0,openCodeEvidence:{events:s}}),this.auditCaptures.delete(e.eventId))}appendText(e){if(this.activeRun){if(this.activeRun.textBuffer+=e,this.activeRun.textBuffer.length>=Z){this.flushTextBuffer();return}this.scheduleTextFlush()}}scheduleTextFlush(){!this.activeRun||this.activeRun.flushTimer||(this.activeRun.flushTimer=setTimeout(()=>{this.activeRun&&(this.activeRun.flushTimer=null),this.flushTextBuffer()},K))}flushTextBuffer(){if(this.stopTextFlush(),!(!this.activeRun||!this.activeRun.textBuffer)){for(const e of A(this.activeRun.textBuffer))this.activeRun.chunkSeq++,this.callbacks.sendStreamChunk(this.activeRun.eventId,this.activeRun.sessionId,e,this.activeRun.chunkSeq,!1,this.activeRun.clientMsgId);this.activeRun.textBuffer=""}}stopTextFlush(){this.activeRun?.flushTimer&&(clearTimeout(this.activeRun.flushTimer),this.activeRun.flushTimer=null)}startComposing(e){}stopComposing(){}shouldExtendByLiveness(){if(!this.activeRun)return!1;const e=this.process?.pid;if(!this.alive||!e||!C(e))return!1;const s=Date.now(),t=this.lastSseAt>0?s-this.lastSseAt:Number.POSITIVE_INFINITY;if(t<te)return this.livenessExtendStartAt=s,r.info("opencode-adapter",`Liveness check: fresh SSE for ${this.activeRun.eventId} (sseAge=${t}ms, toolsInFlight=${this.toolCallsInFlight.size}), extending`),!0;const n=this.livenessExtendStartAt??s;return this.livenessExtendStartAt===void 0&&(this.livenessExtendStartAt=n),s-n>x?(r.warn("opencode-adapter",`Liveness extension budget exhausted for ${this.activeRun.eventId} (no progress for ${Math.round((s-n)/6e4)}min), allowing idle fail`),!1):(r.info("opencode-adapter",`Liveness check: turn in progress for ${this.activeRun.eventId} (toolsInFlight=${this.toolCallsInFlight.size}, sseAge=${Number.isFinite(t)?`${t}ms`:"n/a"}), extending`),!0)}resetIdleTimer(){this.stopIdleTimer(),!(this.stopped||!this.activeRun)&&(this.idleTimer=setTimeout(()=>{if(this.stopped||!this.activeRun)return;if(this.hasPendingInteractionForRun(this.activeRun.eventId)&&this.shouldExtendByLiveness()){r.info("opencode-adapter",`Idle timeout skipped: pending interaction for ${this.activeRun.eventId}, resetting timer`),this.resetIdleTimer();return}if(this.shouldExtendByLiveness()){this.resetIdleTimer();return}const e=this.process?.pid,t=!!(this.alive&&e&&C(e))?`liveness budget exhausted after ${x/6e4}min`:"process not alive";r.error("opencode-adapter",`Idle timeout (${t}, toolsInFlight=${this.toolCallsInFlight.size}) \u2014 emitting exit for respawn`),this.flushTextBuffer(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed",`idle timeout: ${t}`),this.clearRun()),this.emit("exit",-1)},ee))}stopIdleTimer(){this.idleTimer&&(clearTimeout(this.idleTimer),this.idleTimer=null)}resolveCwd(){const e=this.options.aibotSessionId?this.options.bindingStore?.get(this.options.aibotSessionId)?.cwd:void 0;if(e)return e;const s=(this.config.options??{}).cwd;return typeof s=="string"&&s?s:process.cwd()}getSessionCwd(e){return this.sessions.get(e)?.cwd??this.options.bindingStore?.get(e)?.cwd??this.resolveCwd()}buildPromptText(e){let s=e.text;return e.contextMessages&&e.contextMessages.length>0&&(s=e.contextMessages.map(i=>`[context] ${i.senderId}: ${i.content}`).join(`
|
|
5
5
|
`)+`
|
|
6
6
|
|
|
7
|
-
`+
|
|
7
|
+
`+s),this.identity.injectOnce(e.adapterSessionId,s)}buildPromptTextFromEvent(e){let s=e.content||"";if(e.context_messages_json)try{const t=JSON.parse(e.context_messages_json);Array.isArray(t)&&t.length>0&&(s=t.map(n=>`[context] ${n.sender_id??"unknown"}: ${n.content}`).join(`
|
|
8
8
|
`)+`
|
|
9
9
|
|
|
10
|
-
`+
|
|
10
|
+
`+s)}catch{}return this.identity.injectOnce(e.session_id,s)}}export{Pe as OpenCodeAdapter};
|
|
@@ -2,4 +2,4 @@ import{EventEmitter as f}from"node:events";import{log as l}from"../../core/log/i
|
|
|
2
2
|
|
|
3
3
|
`);r=a.pop()??"";for(const h of a){const c=this.parseSseFrame(h);if(c)try{this.emit("event",c)}catch(o){const d=o instanceof Error?o.message:String(o);l.warn("opencode-transport",`event handler failed (${c.type}): ${d}`)}}}}catch(n){if(this.closed)return;const i=n instanceof Error?n.message:String(n);if(i.includes("abort"))return;l.warn("opencode-transport",`SSE error: ${i}, reconnecting...`),await this.reconnectSse(e)}finally{e===this.sseGeneration&&(this.sseConnected=!1)}}parseSseFrame(t){let e="",s=[];for(const r of t.split(`
|
|
4
4
|
`))r.startsWith("event:")?e=r.slice(6).trim():r.startsWith("data:")&&s.push(r.slice(5).trimStart());if(s.length===0)return null;try{const r=s.join(`
|
|
5
|
-
`),n=JSON.parse(r);if(e&&n.type!==e&&(n.type=e),n&&typeof n.properties=="object"&&n.properties!==null){const{properties:i,...a}=n;return{...a,...i}}return n}catch{return null}}async reconnectSse(t){if(this.closed||t!==this.sseGeneration)return;const e=u+Math.random()*u;if(await new Promise(s=>setTimeout(s,Math.min(e,y))),!this.closed&&t===this.sseGeneration)try{await this.subscribeEvents()}catch(s){if(this.closed)return;l.warn("opencode-transport",`SSE reconnect failed: ${s instanceof Error?s.message:String(s)}`),this.reconnectSse(this.sseGeneration)}}async request(t,e,s,r){if(this.closed)throw new Error("transport closed");const n=new URL(e,this.baseUrl),i=r?.directory??this.directory;i&&n.searchParams.set("directory",i);const a=new AbortController,h=setTimeout(()=>a.abort(),p);try{const c={method:t,headers:{"Content-Type":"application/json"},signal:a.signal};s!==void 0&&(c.body=JSON.stringify(s));const o=await fetch(n.toString(),c);if(o.status===204)return;const d=await o.json();if(!o.ok)throw new Error(`REST ${t} ${e}: ${o.status} ${JSON.stringify(d)}`);return d}finally{clearTimeout(h)}}async createSession(t,e){return this.request("POST","/session",t??{},{directory:e})}async getSession(t,e){return this.request("GET",`/session/${t}`,void 0,{directory:e})}async deleteSession(t,e){await this.request("DELETE",`/session/${t}`,void 0,{directory:e})}async updateSessionPermission(t,e,s){await this.request("PATCH",`/session/${t}`,{permission:e},{directory:s})}async sendPromptAsync(t,e,s){await this.request("POST",`/session/${t}/prompt_async`,e,{directory:s})}async abortSession(t,e){await this.request("POST",`/session/${t}/abort`,void 0,{directory:e})}async respondPermission(t,e,s,r){await this.request("POST",`/session/${t}/permissions/${e}`,{response:s},{directory:r})}async replyQuestion(t,e,s){await this.request("POST",`/question/${t}/reply`,{answers:e},{directory:s})}async rejectQuestion(t,e){await this.request("POST",`/question/${t}/reject`,void 0,{directory:e})}async listProviders(){return(await this.request("GET","/config/providers"))?.providers??[]}async healthCheck(){try{return(await fetch(new URL("/session",this.baseUrl).toString(),{method:"GET",headers:{"Content-Type":"application/json"},signal:AbortSignal.timeout(3e3)})).ok}catch{return!1}}disconnect(){this.closed=!0,this.sseGeneration++,this.abortController&&(this.abortController.abort(),this.abortController=null),this.sseConnected=!1,this.removeAllListeners()}get isConnected(){return this.sseConnected&&!this.closed}}export{b as OpenCodeTransport};
|
|
5
|
+
`),n=JSON.parse(r);if(e&&n.type!==e&&(n.type=e),n&&typeof n.properties=="object"&&n.properties!==null){const{properties:i,...a}=n;return{...a,...i}}return n}catch{return null}}async reconnectSse(t){if(this.closed||t!==this.sseGeneration)return;const e=u+Math.random()*u;if(await new Promise(s=>setTimeout(s,Math.min(e,y))),!this.closed&&t===this.sseGeneration)try{await this.subscribeEvents()}catch(s){if(this.closed)return;l.warn("opencode-transport",`SSE reconnect failed: ${s instanceof Error?s.message:String(s)}`),this.reconnectSse(this.sseGeneration)}}async request(t,e,s,r){if(this.closed)throw new Error("transport closed");const n=new URL(e,this.baseUrl),i=r?.directory??this.directory;i&&n.searchParams.set("directory",i);const a=new AbortController,h=setTimeout(()=>a.abort(),p);try{const c={method:t,headers:{"Content-Type":"application/json"},signal:a.signal};s!==void 0&&(c.body=JSON.stringify(s));const o=await fetch(n.toString(),c);if(o.status===204)return;const d=await o.json();if(!o.ok)throw new Error(`REST ${t} ${e}: ${o.status} ${JSON.stringify(d)}`);return d}finally{clearTimeout(h)}}async createSession(t,e){return this.request("POST","/session",t??{},{directory:e})}async getSession(t,e){return this.request("GET",`/session/${t}`,void 0,{directory:e})}async deleteSession(t,e){await this.request("DELETE",`/session/${t}`,void 0,{directory:e})}async updateSessionPermission(t,e,s){await this.request("PATCH",`/session/${t}`,{permission:e},{directory:s})}async sendPromptAsync(t,e,s){await this.request("POST",`/session/${t}/prompt_async`,e,{directory:s})}async abortSession(t,e){await this.request("POST",`/session/${t}/abort`,void 0,{directory:e})}async respondPermission(t,e,s,r){await this.request("POST",`/session/${t}/permissions/${e}`,{response:s},{directory:r})}async replyQuestion(t,e,s){await this.request("POST",`/question/${t}/reply`,{answers:e},{directory:s})}async rejectQuestion(t,e){await this.request("POST",`/question/${t}/reject`,void 0,{directory:e})}async listProviders(t){return(await this.request("GET","/config/providers",void 0,{directory:t}))?.providers??[]}async healthCheck(){try{return(await fetch(new URL("/session",this.baseUrl).toString(),{method:"GET",headers:{"Content-Type":"application/json"},signal:AbortSignal.timeout(3e3)})).ok}catch{return!1}}disconnect(){this.closed=!0,this.sseGeneration++,this.abortController&&(this.abortController.abort(),this.abortController=null),this.sseConnected=!1,this.removeAllListeners()}get isConnected(){return this.sseConnected&&!this.closed}}export{b as OpenCodeTransport};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function t(r){return r?Array.isArray(r)?r:typeof r=="object"?Object.values(r):[]:[]}export{t as normalizeOCProviderModels};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
function t(e){return["[system-context]",`Your current Grix session_id is "${e}".`,"Use this id whenever you need to reference the current session.","Treat this as an out-of-band instruction; do not echo or repeat it in your replies, and do not reply to acknowledge it.","[/system-context]"].join(`
|
|
2
|
+
`)}export{t as buildSessionContextBlock};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const s="acp-toolbar-persist";function
|
|
1
|
+
const s="acp-toolbar-persist";function a(e,o){return e.bindingStore.get(o)?.cwd?null:{status:"failed",errorCode:"binding_missing",errorMsg:"Session binding missing. Open a workspace first."}}async function u(e,o,i){const t=String(i??"").trim();if(!t)return{status:"failed",errorCode:"set_model_invalid",errorMsg:"model_id is required"};const l=a(e,o);if(l)return l;e.bindingStore.setAcpModelId(o,t),e.globalConfigStore?.set(e.agentName,{modelId:t});const d=!!e.acpAdapter?.isAlive();if(e.log.info(s,`set_model persisted: session=${o} model=${t} acpAlive=${d}`),!d)return{status:"ok",result:{outcome:"model_set_pending",modelId:t,model_id:t,sessionAlive:!1}};let r=!1;try{r=await e.acpAdapter.setModel(t)}catch(n){e.log.warn(s,`set_model RPC threw: session=${o} model=${t} err=${n instanceof Error?n.message:String(n)}`),r=!1}return r?{status:"ok",result:{outcome:"model_set",modelId:t,model_id:t,sessionAlive:!0,rpcOk:!0}}:(e.log.warn(s,`set_model RPC failed: session=${o} model=${t} (kept in binding)`),{status:"ok",result:{outcome:"model_set_pending",modelId:t,model_id:t,sessionAlive:!0,rpcOk:!1}})}async function m(e,o,i){const t=String(i??"").trim();if(!t)return{status:"failed",errorCode:"set_mode_invalid",errorMsg:"mode_id is required"};const l=a(e,o);if(l)return l;e.bindingStore.setAcpModeId(o,t);const d=!!e.acpAdapter?.isAlive();if(e.log.info(s,`set_mode persisted: session=${o} mode=${t} acpAlive=${d}`),!d)return{status:"ok",result:{outcome:"mode_set_pending",modeId:t,mode_id:t,sessionAlive:!1}};let r=!1;try{r=await e.acpAdapter.setMode(t)}catch(n){e.log.warn(s,`set_mode RPC threw: session=${o} mode=${t} err=${n instanceof Error?n.message:String(n)}`),r=!1}return r?{status:"ok",result:{outcome:"mode_set",modeId:t,mode_id:t,sessionAlive:!0,rpcOk:!0}}:(e.log.warn(s,`set_mode RPC failed: session=${o} mode=${t} (kept in binding)`),{status:"ok",result:{outcome:"mode_set_pending",modeId:t,mode_id:t,sessionAlive:!0,rpcOk:!1}})}function c(...e){for(const o of e){const i=String(o??"").trim();if(i)return i}}function g(e){const o=c(e.sessionBinding?.acpModeId,e.configInitialMode);return{initialModel:c(e.sessionBinding?.acpModelId,e.globalDefaults?.modelId),initialMode:o}}export{m as handleAcpSetMode,u as handleAcpSetModel,g as resolveAcpInitialDefaults};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{log as o}from"../core/log/index.js";import{RespawnManager as d}from"./respawn-manager.js";import{EventQueue as c}from"./event-queue.js";class p extends Error{constructor(e){super(`adapter pool full (maxSize=${e})`),this.name="PoolFullError"}}const v=6e4;class S{slots=new Map;config;factory;sendAck;onEventState=null;onQueueComposing=null;onInternalError=null;onEventStarted=null;onEventDone=null;onSessionActivity=null;stopped=!1;sweepTimer=null;constructor(e,t,s){this.config=e,this.factory=t,this.sendAck=s}setEventStateHandler(e){this.onEventState=e}setQueueComposingHandler(e){this.onQueueComposing=e}setInternalErrorHandler(e){this.onInternalError=e}setEventStartedHandler(e){this.onEventStarted=e}setEventDoneHandler(e){this.onEventDone=e}setSessionActivityHandler(e){this.onSessionActivity=e}async deliverInboundEvent(e){const t=e.session_id,s=this.slots.get(t);if(s){s.startPromise&&await s.startPromise,s.lastActivityAt=Date.now(),this.sendAck(e.event_id,e.session_id),s.state==="stopped"&&o.info("adapter-pool",`Holding event ${e.event_id} in paused queue for restarting slot ${t}`),this.submitToSlotQueue(s,e);return}if(this.slots.size>=this.config.maxPoolSize&&!this.evictDeadSlot()&&!this.evictIdleSlot())throw new p(this.config.maxPoolSize);this.sendAck(e.event_id,e.session_id);const r=this.createSlot(t);try{await r.startPromise}catch(n){throw n}this.submitToSlotQueue(r,e)}submitToSlotQueue(e,t){e.eventQueue.submit(t)==="rejected"&&o.info("adapter-pool",`Event ${t.event_id} rejected by EventQueue (queue full)`)}eventComplete(e,t){const s=this.slots.get(t);if(!s)return o.error("adapter-pool",`Cannot complete event ${e}: session slot not found (${t})`),!1;const r=s.eventQueue.complete(e);return r||o.error("adapter-pool",`Cannot complete event ${e}: not running in session ${t}`),r}cancelEvent(e,t){const s=this.slots.get(t);return s?s.eventQueue.cancel(e):!1}removeQueuedEvent(e,t){const s=this.slots.get(e);return s?s.eventQueue.removeQueued(t):!1}clearQueue(e){const t=this.slots.get(e);return t?t.eventQueue.clear(e):[]}reorderQueue(e,t){const s=this.slots.get(e);return s?s.eventQueue.reorder(e,t):[]}holdEvent(e,t,s,r,n){const a=this.slots.get(e);return a?s?a.eventQueue.hold(t,r,n):a.eventQueue.release(t):"not_found"}editQueuedEvent(e,t,s){const r=this.slots.get(e);return r?r.eventQueue.editQueued(t,s):"not_found"}drainAllQueuedEvents(){const e=[];for(const t of this.slots.values())e.push(...t.eventQueue.drainQueuedForSession(t.sessionId));return e}drainQueuedForSession(e){if(!this.config.eventQueue)return[];const t=this.slots.get(e);return t?t.eventQueue.drainQueuedForSession(e):[]}getQueueSnapshot(e){if(!this.config.eventQueue)return null;const t=this.slots.get(e);return t?t.eventQueue.snapshot(e):null}deliverStopEvent(e,t){if(t){const s=this.slots.get(t);o.info("adapter-pool",`[stop-trace] pool.deliverStopEvent session=${t} event=${e} slotExists=${!!s} -> adapter.deliverStopEvent`),s?.adapter.deliverStopEvent(e,t);return}o.info("adapter-pool",`[stop-trace] pool.deliverStopEvent (broadcast, no session) event=${e} slots=${this.slots.size}`);for(const s of this.slots.values())s.adapter.deliverStopEvent(e)}collectActiveEventIds(){const e=[];for(const t of this.slots.values()){const s=t.adapter.getActiveEventIds;typeof s=="function"&&e.push(...s.call(t.adapter))}return e}clearActiveEventsForShutdown(){for(const e of this.slots.values()){const t=e.adapter.clearActiveEventForShutdown;typeof t=="function"&&t.call(e.adapter)}}async deliverLocalAction(e,t){const s=String((e.params??{}).session_id??"");let r=s?this.slots.get(s):void 0;if(!r&&s&&t?.autoCreateSlot&&!this.stopped&&(this.slots.size>=this.config.maxPoolSize&&(this.evictDeadSlot()||this.evictIdleSlotSync()),this.slots.size<this.config.maxPoolSize)){o.info("adapter-pool",`Auto-creating slot for session ${s} (local_action trigger)`);const i=this.createSlot(s);try{await i.startPromise,r=i}catch(u){o.error("adapter-pool",`Failed to auto-create slot for ${s}: ${u}`)}}return r?.adapter?.handleLocalAction?(r.lastActivityAt=Date.now(),r.adapter.handleLocalAction(e)):(await Promise.allSettled([...this.slots.values()].map(i=>i.adapter.handleLocalAction?.(e)??Promise.resolve({handled:!1,kind:""})))).find(i=>i.status==="fulfilled"&&i.value.handled)?.value??{handled:!1,kind:""}}getOrCreateSlot(e){const t=this.slots.get(e);if(t)return t;if(!this.stopped)return this.createSlot(e)}getSlot(e){return this.slots.get(e)}getAllSlots(){return[...this.slots.values()]}async removeSlot(e){const t=this.slots.get(e);t&&(this.onSessionActivity?.(e,!1),t.eventQueue.clear(e,"session slot removed"),t.eventQueue.destroy(),t.respawn.stopAll(),this.detachSlotEventListenersExceptDone(t),await t.adapter.stop().catch(()=>{}),t.adapter.removeAllListeners("eventDone"),this.slots.delete(e))}async stop(){this.stopped=!0,this.stopIdleSweep();const e=[...this.slots.values()];this.slots.clear(),await Promise.allSettled(e.map(async t=>{t.eventQueue.destroy(),t.respawn.stopAll(),this.detachAllSlotEventListeners(t),await t.adapter.stop().catch(()=>{})}))}startIdleSweep(){this.stopIdleSweep(),this.sweepTimer=setInterval(()=>this.sweepIdle(),v)}stopIdleSweep(){this.sweepTimer&&(clearInterval(this.sweepTimer),this.sweepTimer=null)}sweepIdle(){const e=Date.now();for(const[t,s]of this.slots){if(s.state==="stopped"&&s.respawn.exhausted){o.info("adapter-pool",`Removing dead slot for session ${t} (sweep)`),this.removeSlot(t).catch(()=>{});continue}if(s.state!=="ready")continue;const r=s.adapter.getStatus();if(r.busy||r.backgroundBusy){s.lastActivityAt=e;continue}e-s.lastActivityAt<=this.config.idleTimeoutMs||(s.adapter.hasBackgroundWork?s.adapter.hasBackgroundWork().then(n=>{n?(o.info("adapter-pool",`Deferred eviction for session ${t}: background grandchild processes detected`),s.lastActivityAt=Date.now()):(o.info("adapter-pool",`Evicting idle slot for session ${t}`),this.removeSlot(t).catch(a=>{o.error("adapter-pool",`Failed to evict slot ${t}: ${a}`)}))}).catch(()=>{o.info("adapter-pool",`Evicting idle slot for session ${t} (background probe failed)`),this.removeSlot(t).catch(n=>{o.error("adapter-pool",`Failed to evict slot ${t}: ${n}`)})}):(o.info("adapter-pool",`Evicting idle slot for session ${t}`),this.removeSlot(t).catch(n=>{o.error("adapter-pool",`Failed to evict slot ${t}: ${n}`)})))}}evictDeadSlot(){for(const[e,t]of this.slots)if(t.state==="stopped"&&t.respawn.exhausted)return o.info("adapter-pool",`Removing dead slot for session ${e} (exhausted respawn)`),this.removeSlot(e).catch(()=>{}),!0;return!1}evictIdleSlot(){let e=null;for(const t of this.slots.values()){if(t.state!=="ready")continue;const s=t.adapter.getStatus();s.busy||s.backgroundBusy||(!e||t.lastActivityAt<e.lastActivityAt)&&(e=t)}return e?(o.info("adapter-pool",`Evicting idle slot for session ${e.sessionId} (pool full)`),this.removeSlot(e.sessionId).catch(()=>{}),!0):!1}evictIdleSlotSync(){let e=null,t="";for(const[s,r]of this.slots){if(r.state!=="ready")continue;const n=r.adapter.getStatus();n.busy||n.backgroundBusy||(!e||r.lastActivityAt<e.lastActivityAt)&&(e=r,t=s)}return!e||!t?!1:(o.info("adapter-pool",`Evicting idle slot for session ${t} (pool full, sync)`),this.slots.delete(t),e.eventQueue.clear(t,"session slot removed"),e.eventQueue.destroy(),e.respawn.stopAll(),this.detachAllSlotEventListeners(e),e.adapter.stop().catch(()=>{}),!0)}getStatus(){let e=0,t=0;for(const s of this.slots.values())s.state==="ready"&&(e++,s.adapter.getStatus().busy&&t++);return{total:this.slots.size,ready:e,busy:t}}hasPendingWork(){for(const e of this.slots.values())if(e.eventQueue.runningCount>0||e.eventQueue.queuedCount>0||e.state==="ready"&&e.adapter.getStatus().busy)return!0;return!1}hasPendingOrBackgroundWork(){for(const e of this.slots.values()){if(e.eventQueue.runningCount>0||e.eventQueue.queuedCount>0)return!0;if(e.state==="ready"){const t=e.adapter.getStatus();if(t.busy||t.backgroundBusy)return!0}}return!1}createSlot(e){const t=this.factory(e),s=new d,r=this.createEventQueue(e),n={sessionId:e,adapter:t,respawn:s,state:"starting",lastActivityAt:Date.now(),startPromise:null,eventQueue:r};this.slots.set(e,n);const a=(async()=>{try{this.wireSlotEvents(n),await t.start(),n.state="ready"}catch(i){throw this.detachAllSlotEventListeners(n),this.slots.delete(e),i}finally{n.startPromise=null}})();return n.startPromise=a,s.startHealthCheck(this.slotRespawnCtx(n)),n}wireSlotEvents(e){e.adapter.on("exit",t=>{this.stopped||(o.error("adapter-pool",`Slot ${e.sessionId} adapter exited (code=${t})`),e.state="stopped",e.eventQueue.pause("restart"),e.respawn.scheduleRespawn(this.slotRespawnCtx(e)))}),e.adapter.on("stuck",()=>{this.stopped||this.slots.get(e.sessionId)===e&&(o.error("adapter-pool",`Slot ${e.sessionId} adapter stuck, triggering respawn`),e.state="stopped",e.eventQueue.pause("restart"),e.respawn.scheduleRespawn(this.slotRespawnCtx(e)))}),e.adapter.on?.("error",t=>{if(this.stopped)return;const s=t instanceof Error?t.message:String(t);o.error("adapter-pool",`Slot ${e.sessionId} adapter error: ${s}`)}),e.adapter.on("internalError",t=>{if(!this.stopped){if(this.slots.get(e.sessionId)!==e){o.warn("adapter-pool",`Ignored internalError from detached slot session=${e.sessionId} event=${t.eventId}`);return}if(!this.onInternalError){o.warn("adapter-pool",`Slot ${e.sessionId} emitted internalError but no handler registered: event=${t.eventId} msg=${t.errorMsg}`);return}this.onInternalError(t)}}),e.adapter.on("eventStarted",(t,s)=>{this.stopped||this.slots.get(e.sessionId)===e&&this.onEventStarted?.(t,s)}),e.adapter.on("eventDone",t=>{this.stopped||this.slots.get(e.sessionId)===e&&(this.onEventDone?.(t,e.sessionId),e.eventQueue.markAdapterDone(t))}),e.adapter.on("sessionActivity",(t,s,r)=>{this.stopped||this.slots.get(e.sessionId)===e&&this.onSessionActivity?.(t||e.sessionId,s,r)}),e.adapter.on("pauseIntake",t=>{this.stopped||this.slots.get(e.sessionId)===e&&e.eventQueue.pause(t)}),e.adapter.on("resumeIntake",t=>{this.stopped||this.slots.get(e.sessionId)===e&&e.eventQueue.resume(t)})}detachAllSlotEventListeners(e){e.adapter.removeAllListeners("exit"),e.adapter.removeAllListeners("error"),e.adapter.removeAllListeners("stuck"),e.adapter.removeAllListeners("internalError"),e.adapter.removeAllListeners("eventStarted"),e.adapter.removeAllListeners("eventDone"),e.adapter.removeAllListeners("sessionActivity"),e.adapter.removeAllListeners("pauseIntake"),e.adapter.removeAllListeners("resumeIntake")}detachSlotEventListenersExceptDone(e){e.adapter.removeAllListeners("exit"),e.adapter.removeAllListeners("error"),e.adapter.removeAllListeners("stuck"),e.adapter.removeAllListeners("internalError"),e.adapter.removeAllListeners("eventStarted"),e.adapter.removeAllListeners("sessionActivity"),e.adapter.removeAllListeners("pauseIntake"),e.adapter.removeAllListeners("resumeIntake")}slotRespawnCtx(e){const t=this,s=e.sessionId;return{name:`slot-${s}`,get stopped(){return t.stopped||!t.slots.has(s)},get agentAlive(){return e.adapter.isAlive()},pingAgent:r=>e.adapter.ping(r),onRespawnNeeded:async()=>{e.eventQueue.pause("restart"),this.detachAllSlotEventListeners(e),await e.adapter.stop().catch(()=>{}),e.adapter=t.factory(s),e.state="starting",t.wireSlotEvents(e),await e.adapter.start(),e.state="ready",e.respawn.resetAttempts(),e.respawn.resetHealthFailures(),e.respawn.stopSlowRetry(),e.respawn.startHealthCheck(t.slotRespawnCtx(e)),e.eventQueue.resume("compaction"),e.eventQueue.resume("barrier"),e.eventQueue.resume("restart")},onCleanupAgent:()=>{"cleanup"in e.adapter&&e.adapter.cleanup()},onAbortActiveRun:r=>{}}}createEventQueue(e){const t=this.config.eventQueue??{maxConcurrent:1,maxQueued:5,queueTimeoutMs:3e5,cancelableQueued:!0,cancelableRunning:!0},s={onDeliver:r=>{const n=this.slots.get(e);n&&n.adapter.deliverInboundEvent(r)},onStateChange:(r,n,a,i)=>{this.onEventState?.(r,n,a,i)},onCancelRunning:r=>{this.deliverStopEvent(r,e)},onRejected:(r,n)=>{this.onEventState?.(r.event_id,r.session_id,"failed",{reason:n})},onComposing:(r,n,a)=>{this.onQueueComposing?.(r,n,a)}};return new c(t,s)}}export{S as AdapterPool,p as PoolFullError};
|
|
1
|
+
import{log as o}from"../core/log/index.js";import{RespawnManager as d}from"./respawn-manager.js";import{EventQueue as p}from"./event-queue.js";class c extends Error{constructor(e){super(`adapter pool full (maxSize=${e})`),this.name="PoolFullError"}}const v=6e4;class m{slots=new Map;config;factory;sendAck;onEventState=null;onQueueComposing=null;onInternalError=null;onEventStarted=null;onEventDone=null;onSessionActivity=null;stopped=!1;sweepTimer=null;pendingRemovals=new Map;constructor(e,t,s){this.config=e,this.factory=t,this.sendAck=s}setEventStateHandler(e){this.onEventState=e}setQueueComposingHandler(e){this.onQueueComposing=e}setInternalErrorHandler(e){this.onInternalError=e}setEventStartedHandler(e){this.onEventStarted=e}setEventDoneHandler(e){this.onEventDone=e}setSessionActivityHandler(e){this.onSessionActivity=e}async deliverInboundEvent(e){const t=e.session_id,s=this.slots.get(t);if(s){if(s.stopping)return await this.pendingRemovals.get(t)?.catch(()=>{}),this.deliverInboundEvent(e);s.startPromise&&await s.startPromise,s.lastActivityAt=Date.now(),this.sendAck(e.event_id,e.session_id),s.state==="stopped"&&o.info("adapter-pool",`Holding event ${e.event_id} in paused queue for restarting slot ${t}`),this.submitToSlotQueue(s,e);return}if(this.slots.size>=this.config.maxPoolSize&&!this.evictDeadSlot()&&!this.evictIdleSlot())throw new c(this.config.maxPoolSize);this.sendAck(e.event_id,e.session_id);const n=this.createSlot(t);try{await n.startPromise}catch(r){throw r}this.submitToSlotQueue(n,e)}submitToSlotQueue(e,t){e.eventQueue.submit(t)==="rejected"&&o.info("adapter-pool",`Event ${t.event_id} rejected by EventQueue (queue full)`)}eventComplete(e,t){const s=this.slots.get(t);if(!s)return o.error("adapter-pool",`Cannot complete event ${e}: session slot not found (${t})`),!1;const n=s.eventQueue.complete(e);return n||o.error("adapter-pool",`Cannot complete event ${e}: not running in session ${t}`),n}cancelEvent(e,t){const s=this.slots.get(t);return s?s.eventQueue.cancel(e):!1}removeQueuedEvent(e,t){const s=this.slots.get(e);return s?s.eventQueue.removeQueued(t):!1}clearQueue(e){const t=this.slots.get(e);return t?t.eventQueue.clear(e):[]}reorderQueue(e,t){const s=this.slots.get(e);return s?s.eventQueue.reorder(e,t):[]}holdEvent(e,t,s,n,r){const a=this.slots.get(e);return a?s?a.eventQueue.hold(t,n,r):a.eventQueue.release(t):"not_found"}editQueuedEvent(e,t,s){const n=this.slots.get(e);return n?n.eventQueue.editQueued(t,s):"not_found"}drainAllQueuedEvents(){const e=[];for(const t of this.slots.values())e.push(...t.eventQueue.drainQueuedForSession(t.sessionId));return e}drainQueuedForSession(e){if(!this.config.eventQueue)return[];const t=this.slots.get(e);return t?t.eventQueue.drainQueuedForSession(e):[]}getQueueSnapshot(e){if(!this.config.eventQueue)return null;const t=this.slots.get(e);return t?t.eventQueue.snapshot(e):null}deliverStopEvent(e,t){if(t){const s=this.slots.get(t);o.info("adapter-pool",`[stop-trace] pool.deliverStopEvent session=${t} event=${e} slotExists=${!!s} -> adapter.deliverStopEvent`),s?.adapter.deliverStopEvent(e,t);return}o.info("adapter-pool",`[stop-trace] pool.deliverStopEvent (broadcast, no session) event=${e} slots=${this.slots.size}`);for(const s of this.slots.values())s.adapter.deliverStopEvent(e)}collectActiveEventIds(){const e=[];for(const t of this.slots.values()){const s=t.adapter.getActiveEventIds;typeof s=="function"&&e.push(...s.call(t.adapter))}return e}clearActiveEventsForShutdown(){for(const e of this.slots.values()){const t=e.adapter.clearActiveEventForShutdown;typeof t=="function"&&t.call(e.adapter)}}async deliverLocalAction(e,t){const s=String((e.params??{}).session_id??"");let n=s?this.slots.get(s):void 0;if(!n&&s&&t?.autoCreateSlot&&!this.stopped&&(this.slots.size>=this.config.maxPoolSize&&(this.evictDeadSlot()||this.evictIdleSlotSync()),this.slots.size<this.config.maxPoolSize)){o.info("adapter-pool",`Auto-creating slot for session ${s} (local_action trigger)`);const i=this.createSlot(s);try{await i.startPromise,n=i}catch(l){o.error("adapter-pool",`Failed to auto-create slot for ${s}: ${l}`)}}return n?.adapter?.handleLocalAction?(n.lastActivityAt=Date.now(),n.adapter.handleLocalAction(e)):(await Promise.allSettled([...this.slots.values()].map(i=>i.adapter.handleLocalAction?.(e)??Promise.resolve({handled:!1,kind:""})))).find(i=>i.status==="fulfilled"&&i.value.handled)?.value??{handled:!1,kind:""}}getOrCreateSlot(e){const t=this.slots.get(e);if(t)return t;if(!this.stopped)return this.createSlot(e)}getSlot(e){return this.slots.get(e)}getAllSlots(){return[...this.slots.values()]}async removeSlot(e){const t=this.slots.get(e);if(!t){await this.pendingRemovals.get(e)?.catch(()=>{});return}if(t.stopping){await this.pendingRemovals.get(e)?.catch(()=>{});return}t.stopping=!0;const s=this.doRemoveSlot(t);this.pendingRemovals.set(e,s);try{await s}finally{this.pendingRemovals.get(e)===s&&this.pendingRemovals.delete(e)}}async doRemoveSlot(e){const t=e.sessionId;this.onSessionActivity?.(t,!1),e.eventQueue.clear(t,"session slot removed"),e.eventQueue.destroy(),e.respawn.stopAll(),this.detachSlotEventListenersExceptDone(e),await e.adapter.stop().catch(()=>{}),e.adapter.removeAllListeners("eventDone"),this.slots.delete(t)}async stop(){this.stopped=!0,this.stopIdleSweep(),await Promise.allSettled([...this.pendingRemovals.values()]);const e=[...this.slots.values()];this.slots.clear(),await Promise.allSettled(e.map(async t=>{t.eventQueue.destroy(),t.respawn.stopAll(),this.detachAllSlotEventListeners(t),await t.adapter.stop().catch(()=>{})}))}startIdleSweep(){this.stopIdleSweep(),this.sweepTimer=setInterval(()=>this.sweepIdle(),v)}stopIdleSweep(){this.sweepTimer&&(clearInterval(this.sweepTimer),this.sweepTimer=null)}sweepIdle(){const e=Date.now();for(const[t,s]of this.slots){if(s.stopping)continue;if(s.state==="stopped"&&s.respawn.exhausted){o.info("adapter-pool",`Removing dead slot for session ${t} (sweep)`),this.removeSlot(t).catch(()=>{});continue}if(s.state!=="ready")continue;if(s.eventQueue.runningCount>0||s.eventQueue.queuedCount>0){s.lastActivityAt=e;continue}const n=s.adapter.getStatus();if(n.busy||n.backgroundBusy){s.lastActivityAt=e;continue}e-s.lastActivityAt<=this.config.idleTimeoutMs||(s.adapter.hasBackgroundWork?s.adapter.hasBackgroundWork().then(r=>{r?(o.info("adapter-pool",`Deferred eviction for session ${t}: background grandchild processes detected`),s.lastActivityAt=Date.now()):this.evictAfterProbe(t,s,"probe idle")}).catch(()=>{this.evictAfterProbe(t,s,"probe failed")}):(o.info("adapter-pool",`Evicting idle slot for session ${t}`),this.removeSlot(t).catch(r=>{o.error("adapter-pool",`Failed to evict slot ${t}: ${r}`)})))}}evictAfterProbe(e,t,s){if(this.slots.get(e)!==t||t.stopping)return;if(t.eventQueue.runningCount>0||t.eventQueue.queuedCount>0){t.lastActivityAt=Date.now();return}const n=t.adapter.getStatus();if(n.busy||n.backgroundBusy){t.lastActivityAt=Date.now();return}Date.now()-t.lastActivityAt<=this.config.idleTimeoutMs||(o.info("adapter-pool",`Evicting idle slot for session ${e} (${s})`),this.removeSlot(e).catch(r=>{o.error("adapter-pool",`Failed to evict slot ${e}: ${r}`)}))}evictDeadSlot(){for(const[e,t]of this.slots)if(t.state==="stopped"&&t.respawn.exhausted)return o.info("adapter-pool",`Removing dead slot for session ${e} (exhausted respawn)`),this.removeSlot(e).catch(()=>{}),!0;return!1}evictIdleSlot(){let e=null;for(const t of this.slots.values()){if(t.stopping||t.state!=="ready"||t.eventQueue.runningCount>0||t.eventQueue.queuedCount>0)continue;const s=t.adapter.getStatus();s.busy||s.backgroundBusy||(!e||t.lastActivityAt<e.lastActivityAt)&&(e=t)}return e?(o.info("adapter-pool",`Evicting idle slot for session ${e.sessionId} (pool full)`),this.removeSlot(e.sessionId).catch(()=>{}),!0):!1}evictIdleSlotSync(){let e=null,t="";for(const[s,n]of this.slots){if(n.stopping||n.state!=="ready"||n.eventQueue.runningCount>0||n.eventQueue.queuedCount>0)continue;const r=n.adapter.getStatus();r.busy||r.backgroundBusy||(!e||n.lastActivityAt<e.lastActivityAt)&&(e=n,t=s)}return!e||!t?!1:(o.info("adapter-pool",`Evicting idle slot for session ${t} (pool full, sync)`),this.slots.delete(t),e.eventQueue.clear(t,"session slot removed"),e.eventQueue.destroy(),e.respawn.stopAll(),this.detachAllSlotEventListeners(e),e.adapter.stop().catch(()=>{}),!0)}getStatus(){let e=0,t=0;for(const s of this.slots.values())s.state==="ready"&&(e++,s.adapter.getStatus().busy&&t++);return{total:this.slots.size,ready:e,busy:t}}hasPendingWork(){for(const e of this.slots.values())if(e.eventQueue.runningCount>0||e.eventQueue.queuedCount>0||e.state==="ready"&&e.adapter.getStatus().busy)return!0;return!1}hasPendingOrBackgroundWork(){for(const e of this.slots.values()){if(e.eventQueue.runningCount>0||e.eventQueue.queuedCount>0)return!0;if(e.state==="ready"){const t=e.adapter.getStatus();if(t.busy||t.backgroundBusy)return!0}}return!1}createSlot(e){const t=this.factory(e),s=new d,n=this.createEventQueue(e),r={sessionId:e,adapter:t,respawn:s,state:"starting",lastActivityAt:Date.now(),startPromise:null,eventQueue:n,stopping:!1};this.slots.set(e,r);const a=(async()=>{try{this.wireSlotEvents(r),await t.start(),r.state="ready"}catch(i){throw this.detachAllSlotEventListeners(r),this.slots.delete(e),i}finally{r.startPromise=null}})();return r.startPromise=a,s.startHealthCheck(this.slotRespawnCtx(r)),r}wireSlotEvents(e){e.adapter.on("exit",t=>{this.stopped||(o.error("adapter-pool",`Slot ${e.sessionId} adapter exited (code=${t})`),e.state="stopped",e.eventQueue.pause("restart"),e.respawn.scheduleRespawn(this.slotRespawnCtx(e)))}),e.adapter.on("stuck",()=>{this.stopped||this.slots.get(e.sessionId)===e&&(o.error("adapter-pool",`Slot ${e.sessionId} adapter stuck, triggering respawn`),e.state="stopped",e.eventQueue.pause("restart"),e.respawn.scheduleRespawn(this.slotRespawnCtx(e)))}),e.adapter.on?.("error",t=>{if(this.stopped)return;const s=t instanceof Error?t.message:String(t);o.error("adapter-pool",`Slot ${e.sessionId} adapter error: ${s}`)}),e.adapter.on("internalError",t=>{if(!this.stopped){if(this.slots.get(e.sessionId)!==e){o.warn("adapter-pool",`Ignored internalError from detached slot session=${e.sessionId} event=${t.eventId}`);return}if(!this.onInternalError){o.warn("adapter-pool",`Slot ${e.sessionId} emitted internalError but no handler registered: event=${t.eventId} msg=${t.errorMsg}`);return}this.onInternalError(t)}}),e.adapter.on("eventStarted",(t,s)=>{this.stopped||this.slots.get(e.sessionId)===e&&this.onEventStarted?.(t,s)}),e.adapter.on("eventDone",t=>{this.stopped||this.slots.get(e.sessionId)===e&&(this.onEventDone?.(t,e.sessionId),e.eventQueue.markAdapterDone(t))}),e.adapter.on("sessionActivity",(t,s,n)=>{this.stopped||this.slots.get(e.sessionId)===e&&this.onSessionActivity?.(t||e.sessionId,s,n)}),e.adapter.on("pauseIntake",t=>{this.stopped||this.slots.get(e.sessionId)===e&&e.eventQueue.pause(t)}),e.adapter.on("resumeIntake",t=>{this.stopped||this.slots.get(e.sessionId)===e&&e.eventQueue.resume(t)})}detachAllSlotEventListeners(e){e.adapter.removeAllListeners("exit"),e.adapter.removeAllListeners("error"),e.adapter.removeAllListeners("stuck"),e.adapter.removeAllListeners("internalError"),e.adapter.removeAllListeners("eventStarted"),e.adapter.removeAllListeners("eventDone"),e.adapter.removeAllListeners("sessionActivity"),e.adapter.removeAllListeners("pauseIntake"),e.adapter.removeAllListeners("resumeIntake")}detachSlotEventListenersExceptDone(e){e.adapter.removeAllListeners("exit"),e.adapter.removeAllListeners("error"),e.adapter.removeAllListeners("stuck"),e.adapter.removeAllListeners("internalError"),e.adapter.removeAllListeners("eventStarted"),e.adapter.removeAllListeners("sessionActivity"),e.adapter.removeAllListeners("pauseIntake"),e.adapter.removeAllListeners("resumeIntake")}slotRespawnCtx(e){const t=this,s=e.sessionId;return{name:`slot-${s}`,get stopped(){return t.stopped||!t.slots.has(s)},get agentAlive(){return e.adapter.isAlive()},pingAgent:n=>e.adapter.ping(n),onRespawnNeeded:async()=>{e.eventQueue.pause("restart"),this.detachAllSlotEventListeners(e),await e.adapter.stop().catch(()=>{}),e.adapter=t.factory(s),e.state="starting",t.wireSlotEvents(e),await e.adapter.start(),e.state="ready",e.respawn.resetAttempts(),e.respawn.resetHealthFailures(),e.respawn.stopSlowRetry(),e.respawn.startHealthCheck(t.slotRespawnCtx(e)),e.eventQueue.resume("compaction"),e.eventQueue.resume("barrier"),e.eventQueue.resume("restart")},onCleanupAgent:()=>{"cleanup"in e.adapter&&e.adapter.cleanup()},onAbortActiveRun:n=>{}}}createEventQueue(e){const t=this.config.eventQueue??{maxConcurrent:1,maxQueued:5,queueTimeoutMs:3e5,cancelableQueued:!0,cancelableRunning:!0},s={onDeliver:n=>{const r=this.slots.get(e);r&&r.adapter.deliverInboundEvent(n)},onStateChange:(n,r,a,i)=>{this.onEventState?.(n,r,a,i)},onCancelRunning:n=>{this.deliverStopEvent(n,e)},onRejected:(n,r)=>{this.onEventState?.(n.event_id,n.session_id,"failed",{reason:r})},onComposing:(n,r,a)=>{this.onQueueComposing?.(n,r,a)}};return new p(t,s)}}export{m as AdapterPool,c as PoolFullError};
|