grix-connector 1.0.2 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter/acp/acp-adapter.js +1 -1
- 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/result-timeout.js +1 -1
- package/dist/adapter/codex/codex-bridge.js +4 -4
- package/dist/adapter/opencode/opencode-adapter.js +2 -2
- package/dist/adapter/openhuman/openhuman-adapter.js +2 -2
- package/dist/adapter/pi/pi-adapter.js +5 -5
- package/dist/core/file-ops/handler.js +1 -1
- package/dist/core/file-ops/list-files.js +1 -1
- package/dist/log.js +2 -2
- package/dist/mcp/stream-http/config.js +1 -1
- package/dist/mcp/stream-http/connection-binding.js +1 -1
- package/dist/mcp/stream-http/gateway.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/package.json +1 -1
|
@@ -10,4 +10,4 @@ ${t}`):t}sendPromptWithRetry(e,t,s=0){this.acpClient.send(t).catch(i=>{if(r.erro
|
|
|
10
10
|
`).trim()}runCommandCapture(e,t,s,i){return new Promise((n,a)=>{const o=R(e,t,{cwd:s,stdio:["ignore","pipe","pipe"]});let c="",u="";const h=setTimeout(()=>{o.kill("SIGTERM"),a(new Error(`${e} timed out after ${i}ms`))},i);o.stdout.on("data",d=>{c+=String(d)}),o.stderr.on("data",d=>{u+=String(d)}),o.on("error",d=>{clearTimeout(h),a(d)}),o.on("close",d=>{if(clearTimeout(h),d===0){n(c||u);return}a(new Error(`${e} exited with code ${d}: ${u||c}`))})})}handleConnectFailure(e){r.error("acp-adapter",`ACP session creation failed: ${f(e)}`),this.acpClient?.removeAllListeners(),this.acpClient=null,this.emit("exit",null)}async startInternalApiAndMcp(){try{this.internalApi||(this.internalApi=new y,this.internalApi.setInvokeHandler(async(t,s)=>this.callbacks.agentInvoke(t,s)),await this.internalApi.start(0),r.info("acp-adapter",`Internal API started at ${this.internalApi.url}`));const e=p.resolve(v,"../../mcp/acp-mcp-server.js");return[{name:"grix-connector-tools",command:process.execPath,args:[e,"--api-url",this.internalApi.url],env:{GRIX_CONNECTOR_INTERNAL_API:this.internalApi.url}}]}catch(e){r.error("acp-adapter",`Failed to start MCP tools: ${e}`);return}}cleanup(){this.activeRun&&this.callbacks.sendSessionComposing(this.activeRun.sessionId,!1),this.pendingApprovals.clear(),this.rejectDeferredEvents("adapter cleaned up"),this.cancelAllDeferredTimers(),this.sessionConnected=!1,this.acpClient&&(this.acpClient.removeAllListeners(),this.acpClient=null),this.agentProcess&&(this.agentProcess.removeAllListeners(),this.agentProcess=null)}async handleAuthRequired(e){r.info("acp-adapter",`Auth required, methods: ${e.authMethods.map(n=>n.id).join(", ")}`);const t=e.authMethods.find(n=>/oauth|browser/i.test(n.id))??e.authMethods[0];if(!t)throw e;const s=this.currentAibotSessionId??"";this.callbacks.sendAuthNotification(s,`Authentication required (${t.id}). Initiating auth flow...`);for(const[n,a]of this.sessionBindings)this.callbacks.sendUpdateBindingCard(n,"failed",a);const i=await this.captureAuthUrl();i&&this.callbacks.sendAuthNotification(s,`Please open this URL to authenticate:
|
|
11
11
|
${i}
|
|
12
12
|
|
|
13
|
-
Waiting for authentication to complete...`);try{await this.acpClient.authenticate(t.id),r.info("acp-adapter","Authentication successful"),this.callbacks.sendAuthNotification(s,"Authentication successful. Resuming...");const n=this.internalApi?[{name:"grix-connector-tools",command:process.execPath,args:[p.resolve(v,"../../mcp/acp-mcp-server.js"),"--api-url",this.internalApi.url],env:{GRIX_CONNECTOR_INTERNAL_API:this.internalApi.url}}]:void 0;await this.acpClient.connect({transport:this.agentProcess.transport,initialMode:this.acpInitialMode,cwd:this.resolveCwd(),mcpServers:n}),r.info("acp-adapter",`ACP session ready after auth: ${this.acpClient.sessionId}`),this.emit("acpSessionReady",this.acpClient.sessionId);for(const[a,o]of this.sessionBindings)this.callbacks.sendUpdateBindingCard(a,"ready",o,this.buildToolbarContext("binding_ready",o))}catch(n){throw r.error("acp-adapter",`Auth retry failed: ${n}`),n}}captureAuthUrl(){return new Promise(e=>{if(!this.agentProcess){e(null);return}const t=/https?:\/\/[^\s"')\]]+/;let s=!1;const i=n=>{if(s)return;const o=n.toString().replace(/\x1b\[[0-9;]*m/g,"").match(t);o&&(s=!0,this.agentProcess.removeListener("stderr",i),e(o[0]))};this.agentProcess.on("stderr",i),setTimeout(()=>{s||(s=!0,this.agentProcess.removeListener("stderr",i),e(null))},3e4)})}handleAcpEvent(e){if(e.type===m.PermissionRequest){this.activeRun&&this.resetIdleTimer(this.activeRun),this.handlePermissionRequest(e);return}const t=this.activeRun;if(t)switch(this.resetIdleTimer(t),t.responded||(t.responded=!0),e.type){case m.Text:{if(e.content){const s=t.quotedStream.consume(e.content);s.quotedMessageId&&(t.quotedMessageId=s.quotedMessageId),s.deltaContent&&this.appendToStream(t,s.deltaContent)}break}case m.ToolUse:{t.awaitingToolResult=!0,e.toolName&&(this.emitRawEventEnvelope(t,{type:"tool_use",payload:{tool_name:e.toolName,tool_input:e.toolInput??""}})||this.callbacks.sendToolUse(t.eventId,t.sessionId,e.toolName,e.toolInput??""));break}case m.ToolResult:{t.awaitingToolResult=!1,e.content&&(this.emitRawEventEnvelope(t,{type:"tool_result",payload:{tool_name:e.toolName??"",content:e.content}})||this.callbacks.sendToolResult(t.eventId,t.sessionId,e.toolName??"",e.content));break}case m.Thinking:{e.content&&(this.emitRawEventEnvelope(t,{type:"thinking",payload:{content:e.content}})||this.callbacks.sendThinking(t.eventId,t.sessionId,e.content));break}case m.Error:{this.emitRawEventEnvelope(t,{type:"error",payload:{message:String(e.error??"unknown error")}}),r.error("acp-adapter",`ACP error: ${e.error}`);break}case m.Result:{this.emitRawEventEnvelope(t,{type:"result",payload:{done:e.done??!0}});const s=t.quotedStream.flush();s.deltaContent&&this.appendToStream(t,s.deltaContent),s.quotedMessageId&&(t.quotedMessageId=s.quotedMessageId),this.flushStream(),this.finishRun("responded");break}}}handlePermissionRequest(e){const t=e.permissionRequest;if(!t||!e.requestId||!this.acpClient)return;if(this.approvalMode==="yolo"||this.approvalMode==="autoEdit"){r.info("acp-adapter",`Auto-approving (${this.approvalMode}): ${t.toolName}`),this.acpClient.respondPermission(t.requestId,{behavior:"allow"}).catch(()=>{});return}const s=t.toolCallId;this.pendingApprovals.set(s,e.requestId);const i=this.activeRun;i?(i.idleTimer&&(clearTimeout(i.idleTimer),i.idleTimer=null),i.composingTimer&&(clearInterval(i.composingTimer),i.composingTimer=null),this.emitRawEventEnvelope(i,{type:"permission_request",payload:{request_id:t.requestId,tool_call_id:s,tool_name:t.toolName,tool_title:t.toolTitle,options:t.options}})||this.callbacks.sendPermissionCard({eventId:i.eventId,sessionId:i.sessionId,toolCallId:s,toolName:t.toolName,toolTitle:t.toolTitle,options:t.options})):(r.info("acp-adapter",`Permission request without active run, auto-approving: ${t.toolName}`),this.acpClient.respondPermission(e.requestId,{behavior:"allow"}),this.pendingApprovals.delete(s))}emitRawEventEnvelope(e,t){return!e||!this.rawTransport||!this.callbacks.sendRawEventEnvelope?!1:(this.callbacks.sendRawEventEnvelope(e.eventId,e.sessionId,{type:t.type,payload:t.payload,seq:++this.rawEventSeq,at:new Date().toISOString()}),!0)}resetIdleTimer(e){e.idleTimer&&clearTimeout(e.idleTimer);const t=e.awaitingToolResult?N:j;e.idleTimer=setTimeout(()=>{this.activeRun?.eventId===e.eventId&&(r.error("acp-adapter",`Agent idle for ${t/1e3}s, declaring stuck: ${e.eventId}`),this.finishRun("failed",`agent idle for ${t/1e3}s`),this.emit("stuck"))},t),e.awaitingToolResult&&!e.composingTimer?e.composingTimer=setInterval(()=>{this.activeRun?.eventId===e.eventId?this.callbacks.sendSessionComposing(e.sessionId,!0):(clearInterval(e.composingTimer),e.composingTimer=null)},25e3):!e.awaitingToolResult&&e.composingTimer&&(clearInterval(e.composingTimer),e.composingTimer=null)}appendToStream(e,t){e.buffer+=t,e.flushTimer||(e.flushTimer=setTimeout(()=>this.flushStream(),q))}emitSegmentedStream(e,t){if(!t)return;const s=e.markdownSegmenter.push(t);for(const i of s)i.text&&(this.callbacks.sendStreamChunk(e.eventId,e.sessionId,i.text,++e.chunkSeq,!1,e.currentClientMsgId),i.closeAfter&&(this.callbacks.sendStreamChunk(e.eventId,e.sessionId,"",++e.chunkSeq,!0,e.currentClientMsgId),e.currentSegmentIndex+=1,e.currentClientMsgId=`${e.clientMsgIdBase}_seg_${e.currentSegmentIndex}`,r.info("acp-adapter",`stream segment rollover event=${e.eventId} segment=${e.currentSegmentIndex} reason=${i.reason??"threshold"}`)))}flushStream(){const e=this.activeRun;if(!e||!e.buffer)return;e.flushTimer&&(clearTimeout(e.flushTimer),e.flushTimer=null);const t=e.buffer;e.buffer="",this.emitSegmentedStream(e,t)}finishRun(e,t){const s=this.activeRun;if(!s)return;this.activeRun=null,this.callbacks.sendSessionComposing(s.sessionId,!1),s.flushTimer&&(clearTimeout(s.flushTimer),s.flushTimer=null),s.idleTimer&&(clearTimeout(s.idleTimer),s.idleTimer=null),s.composingTimer&&(clearInterval(s.composingTimer),s.composingTimer=null);const i=s.quotedStream.flush();i.deltaContent&&(s.buffer+=i.deltaContent),i.quotedMessageId&&(s.quotedMessageId=i.quotedMessageId),s.buffer&&(this.emitSegmentedStream(s,s.buffer),s.buffer=""),t&&this.callbacks.sendRunError(s.eventId,s.sessionId,t);const n=++s.chunkSeq;this.callbacks.sendFinalStreamChunkReliable?this.callbacks.sendFinalStreamChunkReliable(s.eventId,s.sessionId,n,s.currentClientMsgId).then(()=>{s.silent||this.callbacks.sendEventResult(s.eventId,e,t),this.persistEventResult(s,e,t)}).catch(a=>{r.error("acp-adapter",`finalStreamChunk ACK failed event=${s.eventId}: ${a}`),this.callbacks.sendStreamChunk(s.eventId,s.sessionId,"",n,!0,s.currentClientMsgId),s.silent||this.callbacks.sendEventResult(s.eventId,e,t),this.persistEventResult(s,e,t)}):(this.callbacks.sendStreamChunk(s.eventId,s.sessionId,"",n,!0,s.currentClientMsgId),s.silent||this.callbacks.sendEventResult(s.eventId,e,t),this.persistEventResult(s,e,t))}persistEventResult(e,t,s){this.eventResults&&!e.silent&&this.eventResults.set({sessionId:e.sessionId,eventId:e.eventId,status:t,msg:s,updatedAt:Date.now()})}}class L extends S{adapterSessionId;constructor(e){super(),this.adapterSessionId=e}emitDone(e){this.emit("done",e)}emitError(e){if(this.listenerCount("error")===0){r.warn("acp-adapter",`Prompt handle error (no listeners): ${e.message}`);return}this.emit("error",e)}async cancel(){}}export{ie as AcpAdapter};
|
|
13
|
+
Waiting for authentication to complete...`);try{await this.acpClient.authenticate(t.id),r.info("acp-adapter","Authentication successful"),this.callbacks.sendAuthNotification(s,"Authentication successful. Resuming...");const n=this.internalApi?[{name:"grix-connector-tools",command:process.execPath,args:[p.resolve(v,"../../mcp/acp-mcp-server.js"),"--api-url",this.internalApi.url],env:{GRIX_CONNECTOR_INTERNAL_API:this.internalApi.url}}]:void 0;await this.acpClient.connect({transport:this.agentProcess.transport,initialMode:this.acpInitialMode,cwd:this.resolveCwd(),mcpServers:n}),r.info("acp-adapter",`ACP session ready after auth: ${this.acpClient.sessionId}`),this.emit("acpSessionReady",this.acpClient.sessionId);for(const[a,o]of this.sessionBindings)this.callbacks.sendUpdateBindingCard(a,"ready",o,this.buildToolbarContext("binding_ready",o))}catch(n){throw r.error("acp-adapter",`Auth retry failed: ${n}`),n}}captureAuthUrl(){return new Promise(e=>{if(!this.agentProcess){e(null);return}const t=/https?:\/\/[^\s"')\]]+/;let s=!1;const i=n=>{if(s)return;const o=n.toString().replace(/\x1b\[[0-9;]*m/g,"").match(t);o&&(s=!0,this.agentProcess.removeListener("stderr",i),e(o[0]))};this.agentProcess.on("stderr",i),setTimeout(()=>{s||(s=!0,this.agentProcess.removeListener("stderr",i),e(null))},3e4)})}handleAcpEvent(e){if(e.type===m.PermissionRequest){this.activeRun&&this.resetIdleTimer(this.activeRun),this.handlePermissionRequest(e);return}const t=this.activeRun;if(t)switch(this.resetIdleTimer(t),t.responded||(t.responded=!0),e.type){case m.Text:{if(e.content){const s=t.quotedStream.consume(e.content);s.quotedMessageId&&(t.quotedMessageId=s.quotedMessageId),s.deltaContent&&this.appendToStream(t,s.deltaContent)}break}case m.ToolUse:{t.awaitingToolResult=!0,e.toolName&&(this.emitRawEventEnvelope(t,{type:"tool_use",payload:{tool_name:e.toolName,tool_input:e.toolInput??""}})||this.callbacks.sendToolUse(t.eventId,t.sessionId,e.toolName,e.toolInput??""));break}case m.ToolResult:{t.awaitingToolResult=!1,e.content&&(this.emitRawEventEnvelope(t,{type:"tool_result",payload:{tool_name:e.toolName??"",content:e.content}})||this.callbacks.sendToolResult(t.eventId,t.sessionId,e.toolName??"",e.content));break}case m.Thinking:{e.content&&(this.emitRawEventEnvelope(t,{type:"thinking",payload:{content:e.content}})||this.callbacks.sendThinking(t.eventId,t.sessionId,e.content));break}case m.Error:{this.emitRawEventEnvelope(t,{type:"error",payload:{message:String(e.error??"unknown error")}}),r.error("acp-adapter",`ACP error: ${e.error}`);break}case m.Result:{this.emitRawEventEnvelope(t,{type:"result",payload:{done:e.done??!0}});const s=t.quotedStream.flush();s.deltaContent&&this.appendToStream(t,s.deltaContent),s.quotedMessageId&&(t.quotedMessageId=s.quotedMessageId),this.flushStream(),this.finishRun("responded");break}}}handlePermissionRequest(e){const t=e.permissionRequest;if(!t||!e.requestId||!this.acpClient)return;if(this.approvalMode==="yolo"||this.approvalMode==="autoEdit"){r.info("acp-adapter",`Auto-approving (${this.approvalMode}): ${t.toolName}`),this.acpClient.respondPermission(t.requestId,{behavior:"allow"}).catch(()=>{});return}const s=t.toolCallId;this.pendingApprovals.set(s,e.requestId);const i=this.activeRun;i?(i.idleTimer&&(clearTimeout(i.idleTimer),i.idleTimer=null),i.composingTimer&&(clearInterval(i.composingTimer),i.composingTimer=null),this.emitRawEventEnvelope(i,{type:"permission_request",payload:{request_id:t.requestId,tool_call_id:s,tool_name:t.toolName,tool_title:t.toolTitle,options:t.options}})||this.callbacks.sendPermissionCard({eventId:i.eventId,sessionId:i.sessionId,toolCallId:s,toolName:t.toolName,toolTitle:t.toolTitle,options:t.options})):(r.info("acp-adapter",`Permission request without active run, auto-approving: ${t.toolName}`),this.acpClient.respondPermission(e.requestId,{behavior:"allow"}),this.pendingApprovals.delete(s))}emitRawEventEnvelope(e,t){return!e||!this.rawTransport||!this.callbacks.sendRawEventEnvelope?!1:(this.callbacks.sendRawEventEnvelope(e.eventId,e.sessionId,{type:t.type,payload:t.payload,seq:++this.rawEventSeq,at:new Date().toISOString()}),!0)}resetIdleTimer(e){e.idleTimer&&clearTimeout(e.idleTimer);const t=e.awaitingToolResult?N:j;e.idleTimer=setTimeout(()=>{this.activeRun?.eventId===e.eventId&&(r.error("acp-adapter",`Agent idle for ${t/1e3}s, declaring stuck: ${e.eventId}`),this.finishRun("failed",`agent idle for ${t/1e3}s`),this.emit("stuck"))},t),e.awaitingToolResult&&!e.composingTimer?e.composingTimer=setInterval(()=>{this.activeRun?.eventId===e.eventId?this.callbacks.sendSessionComposing(e.sessionId,!0):(clearInterval(e.composingTimer),e.composingTimer=null)},25e3):!e.awaitingToolResult&&e.composingTimer&&(clearInterval(e.composingTimer),e.composingTimer=null)}appendToStream(e,t){e.buffer+=t,e.flushTimer||(e.flushTimer=setTimeout(()=>this.flushStream(),q))}emitSegmentedStream(e,t){if(!t)return;const s=e.markdownSegmenter.push(t);for(const i of s)i.text&&(this.callbacks.sendStreamChunk(e.eventId,e.sessionId,i.text,++e.chunkSeq,!1,e.currentClientMsgId),i.closeAfter&&(this.callbacks.sendStreamChunk(e.eventId,e.sessionId,"",++e.chunkSeq,!0,e.currentClientMsgId),e.currentSegmentIndex+=1,e.currentClientMsgId=`${e.clientMsgIdBase}_seg_${e.currentSegmentIndex}`,r.info("acp-adapter",`stream segment rollover event=${e.eventId} segment=${e.currentSegmentIndex} reason=${i.reason??"threshold"}`)))}flushStream(){const e=this.activeRun;if(!e||!e.buffer)return;e.flushTimer&&(clearTimeout(e.flushTimer),e.flushTimer=null);const t=e.buffer;e.buffer="",this.emitSegmentedStream(e,t)}finishRun(e,t){const s=this.activeRun;if(!s)return;this.activeRun=null,this.emit("eventDone",s.eventId),this.callbacks.sendSessionComposing(s.sessionId,!1),s.flushTimer&&(clearTimeout(s.flushTimer),s.flushTimer=null),s.idleTimer&&(clearTimeout(s.idleTimer),s.idleTimer=null),s.composingTimer&&(clearInterval(s.composingTimer),s.composingTimer=null);const i=s.quotedStream.flush();i.deltaContent&&(s.buffer+=i.deltaContent),i.quotedMessageId&&(s.quotedMessageId=i.quotedMessageId),s.buffer&&(this.emitSegmentedStream(s,s.buffer),s.buffer=""),t&&this.callbacks.sendRunError(s.eventId,s.sessionId,t);const n=++s.chunkSeq;this.callbacks.sendFinalStreamChunkReliable?this.callbacks.sendFinalStreamChunkReliable(s.eventId,s.sessionId,n,s.currentClientMsgId).then(()=>{s.silent||this.callbacks.sendEventResult(s.eventId,e,t),this.persistEventResult(s,e,t)}).catch(a=>{r.error("acp-adapter",`finalStreamChunk ACK failed event=${s.eventId}: ${a}`),this.callbacks.sendStreamChunk(s.eventId,s.sessionId,"",n,!0,s.currentClientMsgId),s.silent||this.callbacks.sendEventResult(s.eventId,e,t),this.persistEventResult(s,e,t)}):(this.callbacks.sendStreamChunk(s.eventId,s.sessionId,"",n,!0,s.currentClientMsgId),s.silent||this.callbacks.sendEventResult(s.eventId,e,t),this.persistEventResult(s,e,t))}persistEventResult(e,t,s){this.eventResults&&!e.silent&&this.eventResults.set({sessionId:e.sessionId,eventId:e.eventId,status:t,msg:s,updatedAt:Date.now()})}}class L extends S{adapterSessionId;constructor(e){super(),this.adapterSessionId=e}emitDone(e){this.emit("done",e)}emitError(e){if(this.listenerCount("error")===0){r.warn("acp-adapter",`Prompt handle error (no listeners): ${e.message}`);return}this.emit("error",e)}async cancel(){}}export{ie as AcpAdapter};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import c from"node:http";import{randomUUID as d}from"node:crypto";import{log as o}from"../../core/log/index.js";function l(t){t.writeHead(401,{"content-type":"application/json"}),t.end(JSON.stringify({error:"unauthorized"}))}function u(t){t.writeHead(404,{"content-type":"application/json"}),t.end(JSON.stringify({error:"not_found"}))}function h(t,e){t.writeHead(400,{"content-type":"application/json"}),t.end(JSON.stringify({error:e}))}function p(t,e={ok:!0}){t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify(e))}async function v(t){const e=[];for await(const
|
|
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
|
-
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};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import{createInterface as P}from"node:readline";import{EventEmitter as T}from"node:events";import{join as y}from"node:path";import{resolveCommandPath as L,spawnCommand as O,killProcessGroup as k}from"../../core/runtime/spawn.js";import{formatInboundMessageReferenceText as N}from"../../core/protocol/message-reference.js";import{log as r}from"../../core/log/index.js";import{resolveClientVersion as j}from"../../core/util/client-version.js";import{isUserVisibleAgentMessagePhase as q}from"../../core/util/codex-output-policy.js";import{SessionBindingStore as
|
|
2
|
-
`)})}catch{return!1}}getStatus(){return{alive:this.alive,busy:this.currentTurnId!==null,sessions:this.threadId?1:0}}getActiveEventIds(){return this.activeEventId?[this.activeEventId]:[]}clearActiveEventForShutdown(){this.clearIdleTimer(),this.activeEventId=null}setModel(e){this.model=e,this.persistCodexContext(),r.info("codex-adapter",`Model set to: ${e}`)}setMode(e){const s=v(e);if(!s){r.info("codex-adapter",`Ignoring unsupported mode: ${e}`);return}this.collaborationMode=s,this.persistCodexContext(),r.info("codex-adapter",`Mode set to: ${s}`)}setReasoningEffort(e){this.reasoningEffort=e,this.persistCodexContext(),r.info("codex-adapter",`Reasoning effort set to: ${e}`)}setSandboxMode(e){this.sandboxMode=e,this.persistCodexContext(),r.info("codex-adapter",`Sandbox mode set to: ${e} (applied on next restart)`)}async threadCompact(){if(!this.threadId)throw new Error("No active thread");return this.sendRequest("thread/compact/start",{threadId:this.threadId},12e4)}async threadRollback(e){if(!this.threadId)throw new Error("No active thread");return this.sendRequest("thread/rollback",{threadId:this.threadId,numTurns:e},1e4)}getThreadId(){return this.threadId}getMcpConfig(){return null}getSupportedCommands(){return[{name:"compact",description:"Compact thread to reduce context size"},{name:"model",description:"List or set model",args:"[model_id]"},{name:"mode",description:"List or set collaboration mode",args:"[default|plan]"},{name:"rollback",description:"Roll back thread turns",args:"[num_turns]"},{name:"interrupt",description:"Interrupt current turn"},{name:"rate_limits",description:"Show current rate limits"},{name:"status",description:"Show thread and session status"},{name:"skills",description:"List available skills"}]}async execCommand(e,s,i){try{switch(e){case"compact":return this.threadId?(this.currentTurnId&&(await this.cancel(this.threadId).catch(()=>{}),this.activeEventId&&this.callbacks.sendEventResult(this.activeEventId,"canceled","compacting"),this.clearActive()),{status:"ok",message:"Thread compacted",data:await this.threadCompact()}):{status:"failed",message:"No active thread"};case"model":{const t=s.trim();return t?this.getModelOptions().some(o=>o.id===t)?(this.setModel(t),{status:"ok",message:`Model set to ${t}`}):{status:"failed",message:`Unknown model: ${t}`}:{status:"ok",message:`Current: ${this.currentModelId()}`,data:{models:this.getModelOptions()}}}case"mode":{const t=s.trim();if(t){const n=v(t);return n?(this.setMode(n),{status:"ok",message:`Mode set to ${n}`}):{status:"failed",message:`Unknown mode: ${t}. Supported: default, plan`}}return{status:"ok",message:`Current: ${this.collaborationMode??"default"}`,data:{modes:
|
|
3
|
-
`):"No skills found",data:t}}default:return{status:"unsupported",message:`Unknown command: ${e}`}}}catch(t){return{status:"failed",message:t instanceof Error?t.message:String(t)}}}async handleLocalAction(e){const s=e.action_type??"",i=e.params??{};switch(r.info("codex-adapter",`handleLocalAction action_type=${s} action_id=${e.action_id} session_id=${i.session_id??""}`),s){case"set_model":{const t=x(l(i.model_id),l(i.modelId),l(i.value));return t&&this.setModel(t),this.callbacks.sendLocalActionResult(e.action_id,"ok",await this.buildToolbarContextResult("model_set")),{handled:!0,kind:"set_model"}}case"set_mode":{const t=i.mode_id;return t&&this.setMode(t),this.callbacks.sendLocalActionResult(e.action_id,"ok",await this.buildToolbarContextResult("mode_set")),{handled:!0,kind:"set_mode"}}case"set_reasoning_effort":{const t=x(l(i.reasoning_effort),l(i.reasoning_eff),l(i.effort));return t&&this.setReasoningEffort(t),this.callbacks.sendLocalActionResult(e.action_id,"ok",await this.buildToolbarContextResult("effort_set",!1)),{handled:!0,kind:"set_reasoning_effort"}}case"set_sandbox_mode":{const t=x(l(i.sandbox_mode),l(i.sandboxMode),l(i.value));return t&&this.setSandboxMode(t),this.callbacks.sendLocalActionResult(e.action_id,"ok",await this.buildToolbarContextResult("sandbox_mode_set",!1)),{handled:!0,kind:"set_sandbox_mode"}}case"thread_compact":{r.info("codex-adapter",`thread_compact start action_id=${e.action_id} threadId=${this.threadId}`);try{const t=await this.threadCompact();r.info("codex-adapter",`thread_compact done action_id=${e.action_id} result=${JSON.stringify(t)}`),this.callbacks.sendLocalActionResult(e.action_id,"ok")}catch(t){r.info("codex-adapter",`thread_compact failed action_id=${e.action_id} err=${t instanceof Error?t.message:String(t)}`),this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,void 0,t instanceof Error?t.message:String(t))}return{handled:!0,kind:"thread_compact"}}case"get_context":return this.callbacks.sendLocalActionResult(e.action_id,"ok",{threadId:this.threadId,model:this.model,mode:this.collaborationMode,approvalPolicy:this.approvalPolicy,cwd:this.cwd,...await this.buildToolbarContextResult("context")}),{handled:!0,kind:"get_context"};case"exec_approve":case"file_approve":{const t=this.resolvePendingApproval(e);if(!t)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:s};const n=i.decision||"allow-once",o=V[n]??"accept";return this.sendApprovalDecision(t.requestId,o),this.pendingApprovals.delete(t.approvalId),this.callbacks.sendLocalActionResult(e.action_id,"ok",{approval_id:t.approvalId,approval_command_id:t.approvalCommandId,decision:n}),this.resumeAfterApproval(e,i,t),{handled:!0,kind:s}}case"exec_reject":case"file_reject":{const t=this.resolvePendingApproval(e);return t?(this.sendApprovalDecision(t.requestId,"deny"),this.pendingApprovals.delete(t.approvalId),this.callbacks.sendLocalActionResult(e.action_id,"ok",{approval_id:t.approvalId,approval_command_id:t.approvalCommandId,decision:"deny"}),this.resumeAfterApproval(e,i,t),{handled:!0,kind:s}):(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"unknown_or_expired_approval_id","That approval request is no longer pending."),{handled:!0,kind:s})}case"permission_approve":{const t=this.resolvePendingApproval(e);return!t||t.kind!=="permission"?(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"unknown_or_expired_approval_id","That permission approval request is no longer pending."),{handled:!0,kind:s}):(this.sendApprovalDecision(t.requestId,"accept"),this.pendingApprovals.delete(t.approvalId),this.callbacks.sendLocalActionResult(e.action_id,"ok",{approval_id:t.approvalId,decision:"approve"}),this.resumeAfterApproval(e,i,t),{handled:!0,kind:s})}case"permission_reject":{const t=this.resolvePendingApproval(e);return!t||t.kind!=="permission"?(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"unknown_or_expired_approval_id","That permission approval request is no longer pending."),{handled:!0,kind:s}):(this.sendApprovalDecision(t.requestId,"deny"),this.pendingApprovals.delete(t.approvalId),this.callbacks.sendLocalActionResult(e.action_id,"ok",{approval_id:t.approvalId,decision:"deny"}),this.resumeAfterApproval(e,i,t),{handled:!0,kind:s})}case"turn_interrupt":{try{await this.cancel(this.threadId??""),this.callbacks.sendLocalActionResult(e.action_id,"ok")}catch(t){this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,void 0,t instanceof Error?t.message:String(t))}return{handled:!0,kind:"turn_interrupt"}}case"thread_rollback":{const t=Number(i.numTurns??i.num_turns??1);if(!this.threadId)return this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"no_thread","No active thread"),{handled:!0,kind:"thread_rollback"};this.currentTurnId&&(await this.cancel(this.threadId).catch(()=>{}),this.activeEventId&&this.callbacks.sendEventResult(this.activeEventId,"canceled","rolled back"),this.clearActive());try{await this.threadRollback(t),this.callbacks.sendLocalActionResult(e.action_id,"ok",{numTurns:t})}catch(n){this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,void 0,n instanceof Error?n.message:String(n))}return{handled:!0,kind:"thread_rollback"}}case"get_rate_limits":{const t=this.rateLimitSnapshot,n=this.buildContextWindowSnapshot(),o=Date.now(),d=this.currentThreadTokenUsage;return t?r.info("codex-adapter",`[rate-limits] responding: primary=${t.primary.usedPercent.toFixed(1)}% window=${t.primary.windowMinutes}min resetsAt=${t.primary.resetsAt} secondary=${t.secondary.usedPercent.toFixed(1)}% window=${t.secondary.windowMinutes}min resetsAt=${t.secondary.resetsAt}`):r.info("codex-adapter","[rate-limits] responding: no rateLimitSnapshot available"),r.debug("codex-adapter",`[cp-diagnose] get_rate_limits: threadId=${this.threadId??"-"} contextWindow=${JSON.stringify(n)} tokenUsage=${JSON.stringify(d)}`),this.callbacks.sendLocalActionResult(e.action_id,"ok",{adapterType:"codex",available:t!==null,cached:!1,sampledAt:o,rateLimits:t,contextWindow:n,tokenUsage:d}),{handled:!0,kind:"get_rate_limits"}}default:return{handled:!1,kind:""}}}async buildToolbarContextResult(e,s=!0){s&&await this.refreshCodexModelOptions();const i=v(this.collaborationMode)??"default",t=this.currentModelId(),n=this.getModelOption(t),o=this.getModelOptions(),d=C,h=this.reasoningEffort??n?.defaultReasoningEffort??null,p=n?.supportedReasoningEfforts??[],u={outcome:e,session_context:{modelId:t,modeId:i,reasoningEffort:h,approvalPolicy:this.approvalPolicy,sandboxMode:this.sandboxMode??null},model_id:t,mode_id:i,currentModelId:t,currentModeId:i,available_models:o,available_modes:d,available_efforts:p,availableModels:o,availableModes:d,reasoning_effort:h,sandbox_mode:this.sandboxMode??null,models:o.map(f=>({modelId:f.id,name:f.displayName})),modes:d.map(f=>({id:f.id,name:f.displayName}))};return this.rateLimitSnapshot&&(u.rate_limit_primary_percent=this.rateLimitSnapshot.primary.usedPercent,u.rate_limit_secondary_percent=this.rateLimitSnapshot.secondary.usedPercent,u.rate_limit_primary_window_min=this.rateLimitSnapshot.primary.windowMinutes,u.rate_limit_secondary_window_min=this.rateLimitSnapshot.secondary.windowMinutes),u}currentModelId(){const e=String(this.model??"").trim();if(e)return e;const s=this.getModelOptions();return s.find(i=>i.isDefault)?.id??s[0]?.id??""}getModelOption(e){const s=e.trim().toLowerCase();return this.getModelOptions().find(i=>i.id.trim().toLowerCase()===s)}getModelOptions(){const e=this.config.options??{},s=e.available_models??e.availableModels??e.models,i=ee(s),t=this.codexModelOptions,n=t.length>0?t:G,o=[],d=String(this.model??"").trim();if(d){const u=[...t,...i].find(f=>f.id.trim().toLowerCase()===d.toLowerCase());o.push({id:d,displayName:u?.displayName??_(d),defaultReasoningEffort:u?.defaultReasoningEffort??null,supportedReasoningEfforts:u?.supportedReasoningEfforts??[],isDefault:u?.isDefault??!1})}o.push(...i),o.push(...n);const h=[],p=new Set;for(const u of o){const f=u.id.trim(),b=f.toLowerCase();!f||p.has(b)||(p.add(b),h.push({id:f,displayName:u.displayName.trim()||f,defaultReasoningEffort:u.defaultReasoningEffort,supportedReasoningEfforts:[...u.supportedReasoningEfforts],isDefault:u.isDefault}))}return h}async refreshCodexModelOptions(){if(!(!this.initialized||!this.alive||!this.process?.stdin))try{const e=[];let s;do{const i=await this.sendRequest("model/list",{cursor:s??null,includeHidden:!1,limit:100},15e3),t=te(i);e.push(...t.models),s=t.nextCursor}while(s);e.length>0&&(this.codexModelOptions=se(e))}catch(e){r.warn("codex-adapter",`Failed to load Codex model list: ${e instanceof Error?e.message:String(e)}`)}}currentTurnId=null;activeEventId=null;activeSessionId=null;visibleAgentMessageIds=new Set;hiddenAgentMessageIds=new Set;agentMessagePhases=new Map;codexSequence=0;deliverInboundEvent(e){const s=N(e.content,{messageId:e.msg_id,quotedMessageId:e.quoted_message_id});if(this.currentTurnId&&s.trim()){r.info("codex-adapter",`Event ${e.event_id}: steering active turn ${this.currentTurnId}`),this.steerTurn(s).catch(i=>{if(r.info("codex-adapter",`Steer failed, falling through: ${i}`),!this.currentTurnId){this.startNewTurn(e,s);return}const t=i instanceof Error?i.message:String(i);this.callbacks.sendEventResult(e.event_id,"failed",`failed to steer message into active turn: ${t}`)});return}if(this.currentTurnId){r.info("codex-adapter",`Event ${e.event_id} rejected: busy and empty content`),this.callbacks.sendEventResult(e.event_id,"failed","agent busy");return}this.startNewTurn(e,s)}startNewTurn(e,s){this.activeEventId=e.event_id,this.activeSessionId=e.session_id,this.visibleAgentMessageIds.clear(),this.hiddenAgentMessageIds.clear(),this.agentMessagePhases.clear(),this.codexSequence=0,this.startComposing();const i=this.threadId??"",t=new S(i),n={adapterSessionId:i,text:s,contextMessages:e.context_messages_json?JSON.parse(e.context_messages_json).map(o=>({senderId:o.sender_id??"unknown",content:o.content})):void 0};this.runTurn(n,t,e.event_id,e.session_id).catch(o=>{r.error("codex-adapter",`Turn failed: ${o}`),this.callbacks.sendEventResult(e.event_id,"failed",o instanceof Error?o.message:String(o)),this.clearActive()}),this.resetIdleTimer(e.event_id)}deliverStopEvent(e,s){this.activeEventId===e&&(this.cancel(this.threadId??"").catch(()=>{}),this.callbacks.sendEventResult(e,"canceled","stopped by user"),this.clearActive())}async spawnCodex(){const e={...process.env,...this.config.env},s=typeof e.PATH=="string"?e.PATH:void 0;s||r.warn("codex-adapter",`spawnCodex: env.PATH is missing! process.env.PATH=${process.env.PATH?"set":"missing"}, config.env=${JSON.stringify(this.config.env)}`);const i=X(this.config.command||"codex",s),t=Y(this.config.args);this.sandboxMode&&t.push("-c",`sandbox_mode="${this.sandboxMode}"`),r.info("codex-adapter",`Spawning: ${i} ${t.join(" ")}`);try{this.process=O(i,t,{env:e,cwd:this.cwd}).process}catch(n){throw r.error("codex-adapter",`Codex spawn threw: ${n}`),this.process=null,this.alive=!1,this.bridgeStatus="closed",n}this.process.on("error",n=>{r.error("codex-adapter",`Codex process spawn error: ${n}`),this.process=null,this.alive=!1,this.bridgeStatus="closed",this.clearIdleTimer(),this.rejectAllPending("spawn failed: "+(n instanceof Error?n.message:String(n))),this.stopComposing(),this.activeEventId&&(this.callbacks.sendEventResult(this.activeEventId,"failed","Codex process spawn failed"),this.activeEventId=null),this.emit("exit",1)}),this.process.on("exit",n=>{r.info("codex-adapter",`Codex process exited (code=${n})`),this.alive=!1,this.bridgeStatus="closed",this.clearIdleTimer(),this.rejectAllPending("process exited"),this.stopComposing(),this.emit("exit",n)}),this.process.stderr?.on("data",n=>{const o=n.toString().trim();o&&r.info("codex-adapter",`[codex stderr] ${o}`)}),this.bindStdout(),this.alive=!0}bindStdout(){if(!this.process?.stdout)return;P({input:this.process.stdout}).on("line",s=>{if(!s.trim())return;let i;try{i=JSON.parse(s)}catch{r.error("codex-adapter",`Invalid JSON from Codex: ${s.slice(0,200)}`);return}if(this.bridgeStatus==="starting"&&(this.bridgeStatus="ready"),i.id!=null){const t=String(i.id),n=this.pendingRequests.get(t);if(n){clearTimeout(n.timer),this.pendingRequests.delete(t),n.resolve(i);return}}this.handleNotification(i)})}handleNotification(e){const s=e.method,i=e.params??{};switch(this.captureContextWindowFromPayload(s,i),this.activeEventId&&this.resetIdleTimer(this.activeEventId),this.activeEventId&&s&&s.startsWith("item/")&&!s.endsWith("/requestApproval")&&this.startComposing(),s){case"item/started":{const t=i.item,n=t?.id,o=t?.type,d=t?.phase;this.isLongRunningToolItemType(o)&&(this.inFlightToolOps+=1),o==="agentMessage"&&n&&(typeof d=="string"&&d.trim()?this.agentMessagePhases.set(n,d.trim().toLowerCase()):this.agentMessagePhases.delete(n),q(d)?(this.visibleAgentMessageIds.add(n),this.hiddenAgentMessageIds.delete(n)):(this.hiddenAgentMessageIds.add(n),this.visibleAgentMessageIds.delete(n)));break}case"item/agentMessage/delta":{const t=i.delta,n=i.itemId;t&&this.activeEventId&&(!n||!this.hiddenAgentMessageIds.has(n))&&!this.shouldSuppressAgentMessageDeltaDuringToolRun(n)&&this.emitCodexEvent("item/agentMessage/delta",e);break}case"item/completed":{const t=i.item,n=t?.id,o=t?.type;this.isLongRunningToolItemType(o)&&(this.inFlightToolOps=Math.max(0,this.inFlightToolOps-1)),this.activeEventId&&o!=="agentMessage"&&this.emitCodexEvent("item/completed",e),o==="agentMessage"&&n&&(this.visibleAgentMessageIds.delete(n),this.hiddenAgentMessageIds.delete(n),this.agentMessagePhases.delete(n));break}case"turn/started":{const n=i.turn?.id;n&&(this.currentTurnId=n,r.info("codex-adapter",`Turn started: ${n}`),this.startComposing());break}case"item/fileChange/requestApproval":{this.handleApprovalRequest(e,"file");break}case"item/commandExecution/requestApproval":{this.handleApprovalRequest(e,"exec");break}case"item/permissions/requestApproval":{this.activeEventId&&this.emitCodexEvent("item/permissions/requestApproval",e),this.handleApprovalRequest(e,"permission");break}case"turn/completed":{this.handleTurnCompleted(i);break}case"error":{const t=i.message??JSON.stringify(i);r.error("codex-adapter",`Codex error: ${t}`);break}case"account/rateLimits/updated":{this.rateLimitSnapshot=this.parseRateLimitSnapshot(i),this.rateLimitSnapshot&&(r.info("codex-adapter",`Rate limits updated: primary=${this.rateLimitSnapshot.primary.usedPercent.toFixed(1)}% secondary=${this.rateLimitSnapshot.secondary.usedPercent.toFixed(1)}%`),this.callbacks.onRateLimitsUpdated?.(this.rateLimitSnapshot));break}case"thread/tokenUsage/updated":{const t=i.threadId,n=i.tokenUsage,o=n?.last;(!t||!n||!o)&&r.info("codex-adapter",`[cp-diagnose] token_usage payload_incomplete: method=thread/tokenUsage/updated hasThreadId=${String(!!t)} hasTokenUsage=${String(!!n)} hasLast=${String(!!o)} params=${JSON.stringify(i)}`),t&&n&&o&&(this.currentThreadTokenUsage={inputTokens:o.inputTokens??0,outputTokens:o.outputTokens??0,cacheReadInputTokens:o.cachedInputTokens??0,cacheCreationInputTokens:0},r.info("codex-adapter",`[cp-diagnose] token_usage raw thread=${t}: ${JSON.stringify(n)}`),r.info("codex-adapter",`[cp-diagnose] token_usage parsed thread=${t}: ${JSON.stringify(this.currentThreadTokenUsage)}`),this.callbacks.onTokenUsageUpdated?.(this.currentThreadTokenUsage),this.callbacks.onContextWindowUpdated?.(this.buildContextWindowSnapshot()));break}default:break}}captureContextWindowFromPayload(e,s){const i=["model_context_window","modelContextWindow","context_window_size","contextWindowSize"],t=this.findNumericField(s,i);if(t==null||t<=0){const n=this.probeContextWindowFields(s,i);n.foundAnyCandidateField&&r.info("codex-adapter",`[cp-diagnose] context_window missing_or_invalid: method=${e??"-"} matchedFields=${n.matchedFields.join(",")||"-"} raw=${n.rawValues.join(",")||"-"} extracted=${String(t)}`);return}this.currentModelContextWindow!==t&&(this.currentModelContextWindow=t,r.info("codex-adapter",`[cp-diagnose] context_window updated: method=${e??"-"} size=${t}`),this.callbacks.onContextWindowUpdated?.(this.buildContextWindowSnapshot()))}probeContextWindowFields(e,s){const i=[],t=[],n=o=>{if(!o||typeof o!="object")return;if(Array.isArray(o)){for(const h of o)n(h);return}const d=o;for(const h of s)Object.prototype.hasOwnProperty.call(d,h)&&(i.push(h),t.push(String(d[h])));for(const h of Object.values(d))n(h)};return n(e),{foundAnyCandidateField:i.length>0,matchedFields:i,rawValues:t}}findNumericField(e,s){if(!e||typeof e!="object")return null;const i=e;for(const t of s){const n=i[t];if(typeof n=="number"&&Number.isFinite(n))return n;if(typeof n=="string"){const o=Number(n);if(Number.isFinite(o))return o}}for(const t of Object.values(i)){if(Array.isArray(t)){for(const o of t){const d=this.findNumericField(o,s);if(d!=null)return d}continue}const n=this.findNumericField(t,s);if(n!=null)return n}return null}buildContextWindowSnapshot(){const e=this.currentModelContextWindow;if(!e||e<=0)return r.debug("codex-adapter",`[cp-diagnose] context_window snapshot skipped: invalid size=${String(e)}`),null;const s=this.currentThreadTokenUsage,i=s?s.inputTokens+s.outputTokens:null,t=i==null?null:Math.max(0,e-i),n=i==null?null:Math.min(100,i/e*100),o=i==null?null:Math.max(0,100-(n??0));return r.info("codex-adapter",`[cp-diagnose] context_window computed: threadId=${this.threadId??"-"} size=${e} usedTokens=${String(i)} usedPercentage=${String(n)}`),{sizeTokens:e,usedTokens:i,remainingTokens:t,usedPercentage:n,remainingPercentage:o,source:"codex:model_context_window"}}parseRateLimitSnapshot(e){try{const i=e.rateLimitsByLimitId?.codex??e.rateLimits??e,t=i.primary,n=i.secondary,o=i.credits,d=c=>{if(typeof c=="number"&&Number.isFinite(c))return c;if(typeof c=="string"){const m=Number(c);if(Number.isFinite(m))return m}return 0},h=c=>typeof c=="boolean"?c:typeof c=="string"?c.toLowerCase()==="true":!1,p=c=>{if(c==null)return null;if(typeof c=="string"){const m=c.trim();if(!m)return null;const I=Number(m);return Number.isFinite(I)&&I>1e9?new Date(I*1e3).toISOString():m}if(typeof c=="number"&&Number.isFinite(c)&&c>0){const m=c>1e12?c/1e3:c;return new Date(m*1e3).toISOString()}return null},u=c=>({usedPercent:d(c.used_percent??c.usedPercent),windowMinutes:d(c.window_minutes??c.windowDurationMins??c.windowMinutes),resetsAt:p(c.resets_at??c.resetsAt)}),f=()=>({usedPercent:0,windowMinutes:0,resetsAt:null}),b=()=>{const c=i.windows;if(!Array.isArray(c))return null;const m=c.map(g=>g&&typeof g=="object"?u(g):f()).filter(g=>g.windowMinutes>0);if(m.length===0)return null;const I=[...m].sort((g,$)=>g.windowMinutes-$.windowMinutes),A=I.find(g=>Math.abs(g.windowMinutes-300)<=30)??I[0],R=I.find(g=>Math.abs(g.windowMinutes-10080)<=120)??I[I.length-1];return{primary:A,secondary:R}},w=(t||n?{primary:t?u(t):f(),secondary:n?u(n):f()}:null)??b();return w?{primary:w.primary,secondary:w.secondary,credits:o?{hasCredits:h(o.has_credits??o.hasCredits),unlimited:h(o.unlimited),balance:o.balance==null?null:d(o.balance)}:{hasCredits:!1,unlimited:!1,balance:null}}:null}catch{return null}}handleApprovalRequest(e,s){const i=e.id;if(i==null)return;const t=e.method,n=`${i}`.trim(),o=`codex_${M(this.activeSessionId??"")}_${M(n)}`;if(this.approvalPolicy==="never"){this.sendApprovalDecision(i,"accept");return}this.activeEventId&&this.emitCodexEvent(t,e),this.pendingApprovals.set(o,{approvalId:o,approvalCommandId:n,sourceEventId:this.activeEventId??"",requestId:i,kind:s}),r.info("codex-adapter",`Pending approval stored: ${o} (kind=${s})`),this.clearIdleTimer(),this.stopComposing()}resolvePendingApproval(e){const s=e.params??{},i=x(s.approval_id,s.approvalId);if(i)return this.pendingApprovals.get(i)??null;const t=x(s.approval_command_id,s.approvalCommandId);if(t){for(const[,n]of this.pendingApprovals)if(n.approvalCommandId===t)return n}return null}sendApprovalDecision(e,s){if(e==null||!this.process?.stdin)return;const i={jsonrpc:"2.0",id:e,result:{decision:s}};this.process.stdin.write(`${JSON.stringify(i)}
|
|
1
|
+
import{createInterface as P}from"node:readline";import{EventEmitter as T}from"node:events";import{join as y}from"node:path";import{resolveCommandPath as L,spawnCommand as O,killProcessGroup as k}from"../../core/runtime/spawn.js";import{formatInboundMessageReferenceText as N}from"../../core/protocol/message-reference.js";import{log as r}from"../../core/log/index.js";import{resolveClientVersion as j}from"../../core/util/client-version.js";import{isUserVisibleAgentMessagePhase as q}from"../../core/util/codex-output-policy.js";import{SessionBindingStore as D}from"../../core/persistence/session-binding-store.js";import{ensureCodexProjectTrusted as F,isCodexCommand as H}from"./codex-trust.js";import{scanSkills as z}from"../claude/skill-scanner.js";const U=60*1e3,W=600*1e3,J=20,B=8e3,G=[{id:"gpt-5.3-codex",displayName:"GPT-5.3 Codex",defaultReasoningEffort:null,supportedReasoningEfforts:[],isDefault:!0}],E=[{id:"default",displayName:"Default"},{id:"plan",displayName:"Plan"}];function X(a,e){const s=String(a??"").trim()||"codex",i=L(s,e);if(i!==s)return i;const t=process.platform==="win32"?[]:["/opt/homebrew/bin/codex","/usr/local/bin/codex","/usr/bin/codex"],n=process.platform!=="win32"?[]:[y(process.env.LOCALAPPDATA??"","npm","codex.cmd"),y(process.env.APPDATA??"","npm","codex.cmd"),y(process.env.LOCALAPPDATA??"","Programs","codex","codex.exe")],o=[...t,...n];for(const d of o)try{return require("node:fs").accessSync(d,require("node:fs").constants.X_OK),d}catch{continue}return r.warn("codex-adapter",`resolveCodexCommandPath: failed to resolve "${s}". envPath=${e?"provided":"missing (using process.env.PATH)"}. fallbacks tried: [${o.join(", ")}]`),s}class ge extends T{type="codex";config;callbacks;process=null;alive=!1;stopped=!1;bridgeStatus="starting";pendingRequests=new Map;requestId=0;threadId=null;initialized=!1;cwd;approvalPolicy;sandboxMode;model;collaborationMode;reasoningEffort;idleTimer=null;inFlightToolOps=0;pendingApprovals=new Map;composingTTL=12e4;composingRefreshInterval=3e4;composingTimer=null;composingTTLClear=null;needsHistoryInjection=!1;lastInjectedSessionId=null;bindingStore=null;aibotSessionId="";threadResumePending=!1;autoTrustProject="auto";codexHome;codexModelOptions=[];rateLimitSnapshot=null;currentThreadTokenUsage=null;currentModelContextWindow=null;constructor(e,s){super(),this.config=e,this.callbacks=s;const i=e.options??{};if(this.approvalPolicy=i.approvalPolicy??"never",this.sandboxMode=typeof i.sandboxMode=="string"&&i.sandboxMode.trim()&&i.sandboxMode.trim()!=="default"?i.sandboxMode.trim():void 0,this.aibotSessionId=String(i.aibotSessionId??"").trim(),this.bindingStore=i.bindingStore instanceof D?i.bindingStore:null,this.model=i.model,this.collaborationMode=i.collaborationMode,this.reasoningEffort=i.reasoningEffort,this.bindingStore&&this.aibotSessionId&&(this.model=this.model??this.bindingStore.getCodexModelId(this.aibotSessionId),this.collaborationMode=this.collaborationMode??this.bindingStore.getCodexModeId(this.aibotSessionId),this.reasoningEffort=this.reasoningEffort??this.bindingStore.getCodexReasoningEffort(this.aibotSessionId),this.sandboxMode=this.sandboxMode??this.bindingStore.getCodexSandboxMode(this.aibotSessionId)),this.autoTrustProject=typeof i.autoTrustProject=="boolean"?i.autoTrustProject:"auto",this.codexHome=typeof i.codexHome=="string"&&i.codexHome.trim()?i.codexHome.trim():void 0,this.cwd=this.resolveCwd(),this.bindingStore&&this.aibotSessionId){const t=this.bindingStore.getCodexThreadId(this.aibotSessionId);t&&(this.threadId=t,this.threadResumePending=!0)}}resolveCwd(){if(this.bindingStore&&this.aibotSessionId){const e=this.bindingStore.get(this.aibotSessionId);if(e?.cwd)return e.cwd}return process.cwd()}async start(){await this.ensureProjectTrusted(),await this.spawnCodex(),await this.initializeHandshake(),await this.notifyBindingReadyWithContext(),r.info("codex-adapter",`Ready (pid=${this.process?.pid})`)}async ensureProjectTrusted(){if(this.shouldAutoTrustProject())try{const e=await F(this.cwd,{codexHome:this.codexHome});e.changed&&r.info("codex-adapter",`Trusted Codex project ${this.cwd} in ${e.configPath}`)}catch(e){const s=e instanceof Error?e.message:String(e);throw r.error("codex-adapter",`Failed to trust Codex project ${this.cwd}: ${s}`),e}}shouldAutoTrustProject(){return this.autoTrustProject===!1?!1:this.autoTrustProject===!0?!0:H(this.config.command)}notifyBindingReady(){!this.aibotSessionId||!this.cwd||this.callbacks.sendUpdateBindingCard(this.aibotSessionId,"ready",this.cwd)}async notifyBindingReadyWithContext(){if(!(!this.aibotSessionId||!this.cwd))try{this.callbacks.sendUpdateBindingCard(this.aibotSessionId,"ready",this.cwd,await this.buildToolbarContextResult("binding_ready"))}catch(e){r.warn("codex-adapter",`Failed to attach toolbar context to binding update: ${e instanceof Error?e.message:String(e)}`),this.notifyBindingReady()}}getEffortMeta(){const e=this.currentModelId(),s=this.getModelOption(e),i=s?.supportedReasoningEfforts??[];return i.length===0?{}:{available_efforts:i,reasoning_effort:this.reasoningEffort??s?.defaultReasoningEffort??null}}async stop(){this.stopped=!0,this.alive=!1,this.stopComposing(),this.clearIdleTimer(),this.rejectAllPending("adapter stopped");const e=this.process;if(this.process=null,e?.pid&&typeof e.once=="function")k(e,"SIGTERM"),await Promise.race([new Promise(i=>{e.once("exit",()=>i(!0))}),new Promise(i=>{setTimeout(()=>i(!1),5e3)})])||k(e,"SIGKILL");else if(e)try{e.kill("SIGTERM")}catch{}}isAlive(){return this.alive}async createSession(e){if(this.initialized||await this.initializeHandshake(),this.threadId||await this.startNewThread(),!this.threadId)throw new Error("Failed to create session: thread ID is missing");return await this.notifyBindingReadyWithContext(),this.threadId}async resumeSession(e,s){await this.ensureThreadResumed()}async destroySession(e){this.threadId=null,this.threadResumePending=!1,this.persistThreadId(void 0)}sendPrompt(e){const s=new S(e.adapterSessionId);return this.runTurn(e,s).catch(i=>{s.emitError(i instanceof Error?i:new Error(String(i)))}),s}async cancel(e){if(this.threadId)try{await this.sendRequest("turn/interrupt",{threadId:this.threadId,turnId:this.currentTurnId??""},5e3)}catch{}}setPermissionHandler(e){this.permissionHandler=e}permissionHandler=null;async ping(e){if(!this.alive||!this.process)return!1;if(!this.process.pid){const s=!!(this.process.stdin&&!this.process.stdin.destroyed),i=!!(this.process.stdout&&!this.process.stdout.destroyed);return s&&i}if(!this.process.stdin||this.process.stdin.destroyed)return!!(this.process.stdout&&!this.process.stdout.destroyed);try{const s=++this.requestId,i={jsonrpc:"2.0",id:s,method:"ping",params:{}};return await new Promise(t=>{const n=setTimeout(()=>{this.pendingRequests.delete(String(s)),t(!1)},e);this.pendingRequests.set(String(s),{resolve:()=>t(!0),reject:()=>t(!1),timer:n}),this.process.stdin.write(`${JSON.stringify(i)}
|
|
2
|
+
`)})}catch{return!1}}getStatus(){return{alive:this.alive,busy:this.currentTurnId!==null,sessions:this.threadId?1:0}}getActiveEventIds(){return this.activeEventId?[this.activeEventId]:[]}clearActiveEventForShutdown(){this.clearIdleTimer(),this.activeEventId=null}setModel(e){this.model=e,this.persistCodexContext(),r.info("codex-adapter",`Model set to: ${e}`)}setMode(e){const s=v(e);if(!s){r.info("codex-adapter",`Ignoring unsupported mode: ${e}`);return}this.collaborationMode=s,this.persistCodexContext(),r.info("codex-adapter",`Mode set to: ${s}`)}setReasoningEffort(e){this.reasoningEffort=e,this.persistCodexContext(),r.info("codex-adapter",`Reasoning effort set to: ${e}`)}setSandboxMode(e){this.sandboxMode=e,this.persistCodexContext(),r.info("codex-adapter",`Sandbox mode set to: ${e} (applied on next restart)`)}async threadCompact(){if(!this.threadId)throw new Error("No active thread");return this.sendRequest("thread/compact/start",{threadId:this.threadId},12e4)}async threadRollback(e){if(!this.threadId)throw new Error("No active thread");return this.sendRequest("thread/rollback",{threadId:this.threadId,numTurns:e},1e4)}getThreadId(){return this.threadId}getMcpConfig(){return null}getSupportedCommands(){return[{name:"compact",description:"Compact thread to reduce context size"},{name:"model",description:"List or set model",args:"[model_id]"},{name:"mode",description:"List or set collaboration mode",args:"[default|plan]"},{name:"rollback",description:"Roll back thread turns",args:"[num_turns]"},{name:"interrupt",description:"Interrupt current turn"},{name:"rate_limits",description:"Show current rate limits"},{name:"status",description:"Show thread and session status"},{name:"skills",description:"List available skills"}]}async execCommand(e,s,i){try{switch(e){case"compact":return this.threadId?(this.currentTurnId&&(await this.cancel(this.threadId).catch(()=>{}),this.activeEventId&&this.callbacks.sendEventResult(this.activeEventId,"canceled","compacting"),this.clearActive()),{status:"ok",message:"Thread compacted",data:await this.threadCompact()}):{status:"failed",message:"No active thread"};case"model":{const t=s.trim();return t?this.getModelOptions().some(o=>o.id===t)?(this.setModel(t),{status:"ok",message:`Model set to ${t}`}):{status:"failed",message:`Unknown model: ${t}`}:{status:"ok",message:`Current: ${this.currentModelId()}`,data:{models:this.getModelOptions()}}}case"mode":{const t=s.trim();if(t){const n=v(t);return n?(this.setMode(n),{status:"ok",message:`Mode set to ${n}`}):{status:"failed",message:`Unknown mode: ${t}. Supported: default, plan`}}return{status:"ok",message:`Current: ${this.collaborationMode??"default"}`,data:{modes:E}}}case"rollback":{if(!this.threadId)return{status:"failed",message:"No active thread"};const t=Math.max(1,parseInt(s.trim(),10)||1);return this.currentTurnId&&(await this.cancel(this.threadId).catch(()=>{}),this.activeEventId&&this.callbacks.sendEventResult(this.activeEventId,"canceled","rolled back"),this.clearActive()),await this.threadRollback(t),{status:"ok",message:`Rolled back ${t} turn(s)`}}case"interrupt":return this.threadId?(await this.cancel(this.threadId),this.activeEventId&&this.callbacks.sendEventResult(this.activeEventId,"canceled","interrupted"),this.clearActive(),{status:"ok",message:"Turn interrupted"}):{status:"failed",message:"No active thread"};case"rate_limits":return{status:"ok",message:this.rateLimitSnapshot?`Primary: ${this.rateLimitSnapshot.primary.usedPercent.toFixed(1)}%, Secondary: ${this.rateLimitSnapshot.secondary.usedPercent.toFixed(1)}%`:"Rate limits not available",data:{rateLimits:this.rateLimitSnapshot}};case"status":return{status:"ok",message:`Thread: ${this.threadId??"none"}, Model: ${this.currentModelId()}, Mode: ${this.collaborationMode??"default"}`,data:{threadId:this.threadId,model:this.currentModelId(),mode:this.collaborationMode??"default",cwd:this.cwd,alive:this.alive}};case"skills":{const t=z({mode:"codex",projectDir:this.cwd}),n=t.map(o=>`- ${o.name}${o.trigger?` (${o.trigger})`:""}: ${o.description}`);return{status:"ok",message:n.length>0?n.join(`
|
|
3
|
+
`):"No skills found",data:t}}default:return{status:"unsupported",message:`Unknown command: ${e}`}}}catch(t){return{status:"failed",message:t instanceof Error?t.message:String(t)}}}async handleLocalAction(e){const s=e.action_type??"",i=e.params??{};switch(r.info("codex-adapter",`handleLocalAction action_type=${s} action_id=${e.action_id} session_id=${i.session_id??""}`),s){case"set_model":{const t=x(l(i.model_id),l(i.modelId),l(i.value));return t&&this.setModel(t),this.callbacks.sendLocalActionResult(e.action_id,"ok",await this.buildToolbarContextResult("model_set")),{handled:!0,kind:"set_model"}}case"set_mode":{const t=i.mode_id;return t&&this.setMode(t),this.callbacks.sendLocalActionResult(e.action_id,"ok",await this.buildToolbarContextResult("mode_set")),{handled:!0,kind:"set_mode"}}case"set_reasoning_effort":{const t=x(l(i.reasoning_effort),l(i.reasoning_eff),l(i.effort));return t&&this.setReasoningEffort(t),this.callbacks.sendLocalActionResult(e.action_id,"ok",await this.buildToolbarContextResult("effort_set",!1)),{handled:!0,kind:"set_reasoning_effort"}}case"set_sandbox_mode":{const t=x(l(i.sandbox_mode),l(i.sandboxMode),l(i.value));return t&&this.setSandboxMode(t),this.callbacks.sendLocalActionResult(e.action_id,"ok",await this.buildToolbarContextResult("sandbox_mode_set",!1)),{handled:!0,kind:"set_sandbox_mode"}}case"thread_compact":{r.info("codex-adapter",`thread_compact start action_id=${e.action_id} threadId=${this.threadId}`);try{const t=await this.threadCompact();r.info("codex-adapter",`thread_compact done action_id=${e.action_id} result=${JSON.stringify(t)}`),this.callbacks.sendLocalActionResult(e.action_id,"ok")}catch(t){r.info("codex-adapter",`thread_compact failed action_id=${e.action_id} err=${t instanceof Error?t.message:String(t)}`),this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,void 0,t instanceof Error?t.message:String(t))}return{handled:!0,kind:"thread_compact"}}case"get_context":return this.callbacks.sendLocalActionResult(e.action_id,"ok",{threadId:this.threadId,model:this.model,mode:this.collaborationMode,approvalPolicy:this.approvalPolicy,cwd:this.cwd,...await this.buildToolbarContextResult("context")}),{handled:!0,kind:"get_context"};case"exec_approve":case"file_approve":{const t=this.resolvePendingApproval(e);if(!t)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:s};const n=i.decision||"allow-once",o=V[n]??"accept";return this.sendApprovalDecision(t.requestId,o),this.pendingApprovals.delete(t.approvalId),this.callbacks.sendLocalActionResult(e.action_id,"ok",{approval_id:t.approvalId,approval_command_id:t.approvalCommandId,decision:n}),this.resumeAfterApproval(e,i,t),{handled:!0,kind:s}}case"exec_reject":case"file_reject":{const t=this.resolvePendingApproval(e);return t?(this.sendApprovalDecision(t.requestId,"deny"),this.pendingApprovals.delete(t.approvalId),this.callbacks.sendLocalActionResult(e.action_id,"ok",{approval_id:t.approvalId,approval_command_id:t.approvalCommandId,decision:"deny"}),this.resumeAfterApproval(e,i,t),{handled:!0,kind:s}):(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"unknown_or_expired_approval_id","That approval request is no longer pending."),{handled:!0,kind:s})}case"permission_approve":{const t=this.resolvePendingApproval(e);return!t||t.kind!=="permission"?(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"unknown_or_expired_approval_id","That permission approval request is no longer pending."),{handled:!0,kind:s}):(this.sendApprovalDecision(t.requestId,"accept"),this.pendingApprovals.delete(t.approvalId),this.callbacks.sendLocalActionResult(e.action_id,"ok",{approval_id:t.approvalId,decision:"approve"}),this.resumeAfterApproval(e,i,t),{handled:!0,kind:s})}case"permission_reject":{const t=this.resolvePendingApproval(e);return!t||t.kind!=="permission"?(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"unknown_or_expired_approval_id","That permission approval request is no longer pending."),{handled:!0,kind:s}):(this.sendApprovalDecision(t.requestId,"deny"),this.pendingApprovals.delete(t.approvalId),this.callbacks.sendLocalActionResult(e.action_id,"ok",{approval_id:t.approvalId,decision:"deny"}),this.resumeAfterApproval(e,i,t),{handled:!0,kind:s})}case"turn_interrupt":{try{await this.cancel(this.threadId??""),this.callbacks.sendLocalActionResult(e.action_id,"ok")}catch(t){this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,void 0,t instanceof Error?t.message:String(t))}return{handled:!0,kind:"turn_interrupt"}}case"thread_rollback":{const t=Number(i.numTurns??i.num_turns??1);if(!this.threadId)return this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"no_thread","No active thread"),{handled:!0,kind:"thread_rollback"};this.currentTurnId&&(await this.cancel(this.threadId).catch(()=>{}),this.activeEventId&&this.callbacks.sendEventResult(this.activeEventId,"canceled","rolled back"),this.clearActive());try{await this.threadRollback(t),this.callbacks.sendLocalActionResult(e.action_id,"ok",{numTurns:t})}catch(n){this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,void 0,n instanceof Error?n.message:String(n))}return{handled:!0,kind:"thread_rollback"}}case"get_rate_limits":{const t=this.rateLimitSnapshot,n=this.buildContextWindowSnapshot(),o=Date.now(),d=this.currentThreadTokenUsage;return t?r.info("codex-adapter",`[rate-limits] responding: primary=${t.primary.usedPercent.toFixed(1)}% window=${t.primary.windowMinutes}min resetsAt=${t.primary.resetsAt} secondary=${t.secondary.usedPercent.toFixed(1)}% window=${t.secondary.windowMinutes}min resetsAt=${t.secondary.resetsAt}`):r.info("codex-adapter","[rate-limits] responding: no rateLimitSnapshot available"),r.debug("codex-adapter",`[cp-diagnose] get_rate_limits: threadId=${this.threadId??"-"} contextWindow=${JSON.stringify(n)} tokenUsage=${JSON.stringify(d)}`),this.callbacks.sendLocalActionResult(e.action_id,"ok",{adapterType:"codex",available:t!==null,cached:!1,sampledAt:o,rateLimits:t,contextWindow:n,tokenUsage:d}),{handled:!0,kind:"get_rate_limits"}}default:return{handled:!1,kind:""}}}async buildToolbarContextResult(e,s=!0){s&&await this.refreshCodexModelOptions();const i=v(this.collaborationMode)??"default",t=this.currentModelId(),n=this.getModelOption(t),o=this.getModelOptions(),d=E,h=this.reasoningEffort??n?.defaultReasoningEffort??null,p=n?.supportedReasoningEfforts??[],u={outcome:e,session_context:{modelId:t,modeId:i,reasoningEffort:h,approvalPolicy:this.approvalPolicy,sandboxMode:this.sandboxMode??null},model_id:t,mode_id:i,currentModelId:t,currentModeId:i,available_models:o,available_modes:d,available_efforts:p,availableModels:o,availableModes:d,reasoning_effort:h,sandbox_mode:this.sandboxMode??null,models:o.map(f=>({modelId:f.id,name:f.displayName})),modes:d.map(f=>({id:f.id,name:f.displayName}))};return this.rateLimitSnapshot&&(u.rate_limit_primary_percent=this.rateLimitSnapshot.primary.usedPercent,u.rate_limit_secondary_percent=this.rateLimitSnapshot.secondary.usedPercent,u.rate_limit_primary_window_min=this.rateLimitSnapshot.primary.windowMinutes,u.rate_limit_secondary_window_min=this.rateLimitSnapshot.secondary.windowMinutes),u}currentModelId(){const e=String(this.model??"").trim();if(e)return e;const s=this.getModelOptions();return s.find(i=>i.isDefault)?.id??s[0]?.id??""}getModelOption(e){const s=e.trim().toLowerCase();return this.getModelOptions().find(i=>i.id.trim().toLowerCase()===s)}getModelOptions(){const e=this.config.options??{},s=e.available_models??e.availableModels??e.models,i=ee(s),t=this.codexModelOptions,n=t.length>0?t:G,o=[],d=String(this.model??"").trim();if(d){const u=[...t,...i].find(f=>f.id.trim().toLowerCase()===d.toLowerCase());o.push({id:d,displayName:u?.displayName??_(d),defaultReasoningEffort:u?.defaultReasoningEffort??null,supportedReasoningEfforts:u?.supportedReasoningEfforts??[],isDefault:u?.isDefault??!1})}o.push(...i),o.push(...n);const h=[],p=new Set;for(const u of o){const f=u.id.trim(),b=f.toLowerCase();!f||p.has(b)||(p.add(b),h.push({id:f,displayName:u.displayName.trim()||f,defaultReasoningEffort:u.defaultReasoningEffort,supportedReasoningEfforts:[...u.supportedReasoningEfforts],isDefault:u.isDefault}))}return h}async refreshCodexModelOptions(){if(!(!this.initialized||!this.alive||!this.process?.stdin))try{const e=[];let s;do{const i=await this.sendRequest("model/list",{cursor:s??null,includeHidden:!1,limit:100},15e3),t=te(i);e.push(...t.models),s=t.nextCursor}while(s);e.length>0&&(this.codexModelOptions=se(e))}catch(e){r.warn("codex-adapter",`Failed to load Codex model list: ${e instanceof Error?e.message:String(e)}`)}}currentTurnId=null;activeEventId=null;activeSessionId=null;visibleAgentMessageIds=new Set;hiddenAgentMessageIds=new Set;agentMessagePhases=new Map;codexSequence=0;deliverInboundEvent(e){const s=N(e.content,{messageId:e.msg_id,quotedMessageId:e.quoted_message_id});if(this.currentTurnId&&s.trim()){r.info("codex-adapter",`Event ${e.event_id}: steering active turn ${this.currentTurnId}`),this.steerTurn(s).catch(i=>{if(r.info("codex-adapter",`Steer failed, falling through: ${i}`),!this.currentTurnId){this.startNewTurn(e,s);return}const t=i instanceof Error?i.message:String(i);this.callbacks.sendEventResult(e.event_id,"failed",`failed to steer message into active turn: ${t}`)});return}if(this.currentTurnId){r.info("codex-adapter",`Event ${e.event_id} rejected: busy and empty content`),this.callbacks.sendEventResult(e.event_id,"failed","agent busy");return}this.startNewTurn(e,s)}startNewTurn(e,s){this.activeEventId=e.event_id,this.activeSessionId=e.session_id,this.visibleAgentMessageIds.clear(),this.hiddenAgentMessageIds.clear(),this.agentMessagePhases.clear(),this.codexSequence=0,this.startComposing();const i=this.threadId??"",t=new S(i),n={adapterSessionId:i,text:s,contextMessages:e.context_messages_json?JSON.parse(e.context_messages_json).map(o=>({senderId:o.sender_id??"unknown",content:o.content})):void 0};this.runTurn(n,t,e.event_id,e.session_id).catch(o=>{r.error("codex-adapter",`Turn failed: ${o}`),this.callbacks.sendEventResult(e.event_id,"failed",o instanceof Error?o.message:String(o)),this.clearActive()}),this.resetIdleTimer(e.event_id)}deliverStopEvent(e,s){this.activeEventId===e&&(this.cancel(this.threadId??"").catch(()=>{}),this.callbacks.sendEventResult(e,"canceled","stopped by user"),this.clearActive())}async spawnCodex(){const e={...process.env,...this.config.env},s=typeof e.PATH=="string"?e.PATH:void 0;s||r.warn("codex-adapter",`spawnCodex: env.PATH is missing! process.env.PATH=${process.env.PATH?"set":"missing"}, config.env=${JSON.stringify(this.config.env)}`);const i=X(this.config.command||"codex",s),t=Y(this.config.args);this.sandboxMode&&t.push("-c",`sandbox_mode="${this.sandboxMode}"`),r.info("codex-adapter",`Spawning: ${i} ${t.join(" ")}`);try{this.process=O(i,t,{env:e,cwd:this.cwd}).process}catch(n){throw r.error("codex-adapter",`Codex spawn threw: ${n}`),this.process=null,this.alive=!1,this.bridgeStatus="closed",n}this.process.on("error",n=>{r.error("codex-adapter",`Codex process spawn error: ${n}`),this.process=null,this.alive=!1,this.bridgeStatus="closed",this.clearIdleTimer(),this.rejectAllPending("spawn failed: "+(n instanceof Error?n.message:String(n))),this.stopComposing(),this.activeEventId&&(this.callbacks.sendEventResult(this.activeEventId,"failed","Codex process spawn failed"),this.activeEventId=null),this.emit("exit",1)}),this.process.on("exit",n=>{r.info("codex-adapter",`Codex process exited (code=${n})`),this.alive=!1,this.bridgeStatus="closed",this.clearIdleTimer(),this.rejectAllPending("process exited"),this.stopComposing(),this.emit("exit",n)}),this.process.stderr?.on("data",n=>{const o=n.toString().trim();o&&r.info("codex-adapter",`[codex stderr] ${o}`)}),this.bindStdout(),this.alive=!0}bindStdout(){if(!this.process?.stdout)return;P({input:this.process.stdout}).on("line",s=>{if(!s.trim())return;let i;try{i=JSON.parse(s)}catch{r.error("codex-adapter",`Invalid JSON from Codex: ${s.slice(0,200)}`);return}if(this.bridgeStatus==="starting"&&(this.bridgeStatus="ready"),i.id!=null){const t=String(i.id),n=this.pendingRequests.get(t);if(n){clearTimeout(n.timer),this.pendingRequests.delete(t),n.resolve(i);return}}this.handleNotification(i)})}handleNotification(e){const s=e.method,i=e.params??{};switch(this.captureContextWindowFromPayload(s,i),this.activeEventId&&this.resetIdleTimer(this.activeEventId),this.activeEventId&&s&&s.startsWith("item/")&&!s.endsWith("/requestApproval")&&this.startComposing(),s){case"item/started":{const t=i.item,n=t?.id,o=t?.type,d=t?.phase;this.isLongRunningToolItemType(o)&&(this.inFlightToolOps+=1),o==="agentMessage"&&n&&(typeof d=="string"&&d.trim()?this.agentMessagePhases.set(n,d.trim().toLowerCase()):this.agentMessagePhases.delete(n),q(d)?(this.visibleAgentMessageIds.add(n),this.hiddenAgentMessageIds.delete(n)):(this.hiddenAgentMessageIds.add(n),this.visibleAgentMessageIds.delete(n)));break}case"item/agentMessage/delta":{const t=i.delta,n=i.itemId;t&&this.activeEventId&&(!n||!this.hiddenAgentMessageIds.has(n))&&!this.shouldSuppressAgentMessageDeltaDuringToolRun(n)&&this.emitCodexEvent("item/agentMessage/delta",e);break}case"item/completed":{const t=i.item,n=t?.id,o=t?.type;this.isLongRunningToolItemType(o)&&(this.inFlightToolOps=Math.max(0,this.inFlightToolOps-1)),this.activeEventId&&o!=="agentMessage"&&this.emitCodexEvent("item/completed",e),o==="agentMessage"&&n&&(this.visibleAgentMessageIds.delete(n),this.hiddenAgentMessageIds.delete(n),this.agentMessagePhases.delete(n));break}case"turn/started":{const n=i.turn?.id;n&&(this.currentTurnId=n,r.info("codex-adapter",`Turn started: ${n}`),this.startComposing());break}case"item/fileChange/requestApproval":{this.handleApprovalRequest(e,"file");break}case"item/commandExecution/requestApproval":{this.handleApprovalRequest(e,"exec");break}case"item/permissions/requestApproval":{this.activeEventId&&this.emitCodexEvent("item/permissions/requestApproval",e),this.handleApprovalRequest(e,"permission");break}case"turn/completed":{this.handleTurnCompleted(i);break}case"error":{const t=i.message??JSON.stringify(i);r.error("codex-adapter",`Codex error: ${t}`);break}case"account/rateLimits/updated":{this.rateLimitSnapshot=this.parseRateLimitSnapshot(i),this.rateLimitSnapshot&&(r.info("codex-adapter",`Rate limits updated: primary=${this.rateLimitSnapshot.primary.usedPercent.toFixed(1)}% secondary=${this.rateLimitSnapshot.secondary.usedPercent.toFixed(1)}%`),this.callbacks.onRateLimitsUpdated?.(this.rateLimitSnapshot));break}case"thread/tokenUsage/updated":{const t=i.threadId,n=i.tokenUsage,o=n?.last;(!t||!n||!o)&&r.info("codex-adapter",`[cp-diagnose] token_usage payload_incomplete: method=thread/tokenUsage/updated hasThreadId=${String(!!t)} hasTokenUsage=${String(!!n)} hasLast=${String(!!o)} params=${JSON.stringify(i)}`),t&&n&&o&&(this.currentThreadTokenUsage={inputTokens:o.inputTokens??0,outputTokens:o.outputTokens??0,cacheReadInputTokens:o.cachedInputTokens??0,cacheCreationInputTokens:0},r.info("codex-adapter",`[cp-diagnose] token_usage raw thread=${t}: ${JSON.stringify(n)}`),r.info("codex-adapter",`[cp-diagnose] token_usage parsed thread=${t}: ${JSON.stringify(this.currentThreadTokenUsage)}`),this.callbacks.onTokenUsageUpdated?.(this.currentThreadTokenUsage),this.callbacks.onContextWindowUpdated?.(this.buildContextWindowSnapshot()));break}default:break}}captureContextWindowFromPayload(e,s){const i=["model_context_window","modelContextWindow","context_window_size","contextWindowSize"],t=this.findNumericField(s,i);if(t==null||t<=0){const n=this.probeContextWindowFields(s,i);n.foundAnyCandidateField&&r.info("codex-adapter",`[cp-diagnose] context_window missing_or_invalid: method=${e??"-"} matchedFields=${n.matchedFields.join(",")||"-"} raw=${n.rawValues.join(",")||"-"} extracted=${String(t)}`);return}this.currentModelContextWindow!==t&&(this.currentModelContextWindow=t,r.info("codex-adapter",`[cp-diagnose] context_window updated: method=${e??"-"} size=${t}`),this.callbacks.onContextWindowUpdated?.(this.buildContextWindowSnapshot()))}probeContextWindowFields(e,s){const i=[],t=[],n=o=>{if(!o||typeof o!="object")return;if(Array.isArray(o)){for(const h of o)n(h);return}const d=o;for(const h of s)Object.prototype.hasOwnProperty.call(d,h)&&(i.push(h),t.push(String(d[h])));for(const h of Object.values(d))n(h)};return n(e),{foundAnyCandidateField:i.length>0,matchedFields:i,rawValues:t}}findNumericField(e,s){if(!e||typeof e!="object")return null;const i=e;for(const t of s){const n=i[t];if(typeof n=="number"&&Number.isFinite(n))return n;if(typeof n=="string"){const o=Number(n);if(Number.isFinite(o))return o}}for(const t of Object.values(i)){if(Array.isArray(t)){for(const o of t){const d=this.findNumericField(o,s);if(d!=null)return d}continue}const n=this.findNumericField(t,s);if(n!=null)return n}return null}buildContextWindowSnapshot(){const e=this.currentModelContextWindow;if(!e||e<=0)return r.debug("codex-adapter",`[cp-diagnose] context_window snapshot skipped: invalid size=${String(e)}`),null;const s=this.currentThreadTokenUsage,i=s?s.inputTokens+s.outputTokens:null,t=i==null?null:Math.max(0,e-i),n=i==null?null:Math.min(100,i/e*100),o=i==null?null:Math.max(0,100-(n??0));return r.info("codex-adapter",`[cp-diagnose] context_window computed: threadId=${this.threadId??"-"} size=${e} usedTokens=${String(i)} usedPercentage=${String(n)}`),{sizeTokens:e,usedTokens:i,remainingTokens:t,usedPercentage:n,remainingPercentage:o,source:"codex:model_context_window"}}parseRateLimitSnapshot(e){try{const i=e.rateLimitsByLimitId?.codex??e.rateLimits??e,t=i.primary,n=i.secondary,o=i.credits,d=c=>{if(typeof c=="number"&&Number.isFinite(c))return c;if(typeof c=="string"){const m=Number(c);if(Number.isFinite(m))return m}return 0},h=c=>typeof c=="boolean"?c:typeof c=="string"?c.toLowerCase()==="true":!1,p=c=>{if(c==null)return null;if(typeof c=="string"){const m=c.trim();if(!m)return null;const I=Number(m);return Number.isFinite(I)&&I>1e9?new Date(I*1e3).toISOString():m}if(typeof c=="number"&&Number.isFinite(c)&&c>0){const m=c>1e12?c/1e3:c;return new Date(m*1e3).toISOString()}return null},u=c=>({usedPercent:d(c.used_percent??c.usedPercent),windowMinutes:d(c.window_minutes??c.windowDurationMins??c.windowMinutes),resetsAt:p(c.resets_at??c.resetsAt)}),f=()=>({usedPercent:0,windowMinutes:0,resetsAt:null}),b=()=>{const c=i.windows;if(!Array.isArray(c))return null;const m=c.map(g=>g&&typeof g=="object"?u(g):f()).filter(g=>g.windowMinutes>0);if(m.length===0)return null;const I=[...m].sort((g,$)=>g.windowMinutes-$.windowMinutes),A=I.find(g=>Math.abs(g.windowMinutes-300)<=30)??I[0],R=I.find(g=>Math.abs(g.windowMinutes-10080)<=120)??I[I.length-1];return{primary:A,secondary:R}},w=(t||n?{primary:t?u(t):f(),secondary:n?u(n):f()}:null)??b();return w?{primary:w.primary,secondary:w.secondary,credits:o?{hasCredits:h(o.has_credits??o.hasCredits),unlimited:h(o.unlimited),balance:o.balance==null?null:d(o.balance)}:{hasCredits:!1,unlimited:!1,balance:null}}:null}catch{return null}}handleApprovalRequest(e,s){const i=e.id;if(i==null)return;const t=e.method,n=`${i}`.trim(),o=`codex_${M(this.activeSessionId??"")}_${M(n)}`;if(this.approvalPolicy==="never"){this.sendApprovalDecision(i,"accept");return}this.activeEventId&&this.emitCodexEvent(t,e),this.pendingApprovals.set(o,{approvalId:o,approvalCommandId:n,sourceEventId:this.activeEventId??"",requestId:i,kind:s}),r.info("codex-adapter",`Pending approval stored: ${o} (kind=${s})`),this.clearIdleTimer(),this.stopComposing()}resolvePendingApproval(e){const s=e.params??{},i=x(s.approval_id,s.approvalId);if(i)return this.pendingApprovals.get(i)??null;const t=x(s.approval_command_id,s.approvalCommandId);if(t){for(const[,n]of this.pendingApprovals)if(n.approvalCommandId===t)return n}return null}sendApprovalDecision(e,s){if(e==null||!this.process?.stdin)return;const i={jsonrpc:"2.0",id:e,result:{decision:s}};this.process.stdin.write(`${JSON.stringify(i)}
|
|
4
4
|
`)}handleTurnCompleted(e){if(this.currentTurnId=null,this.stopComposing(),this.activeEventId){const s=this.activeEventId,i={event_id:s,session_id:this.activeSessionId??"",thread_id:this.threadId??void 0,codex_event_type:"codex",codex_method:"turn/completed",codex_sequence:++this.codexSequence,codex_payload:e,codex_at:new Date().toISOString()};this.callbacks.sendCodexEventReliable?(this.clearActive(),this.callbacks.sendCodexEventReliable(i).then(()=>{this.callbacks.sendEventResult(s,"responded")}).catch(t=>{r.error("codex-adapter",`sendCodexEventReliable failed event=${s}: ${t}`),this.callbacks.sendCodexEvent(i),this.callbacks.sendEventResult(s,"responded")})):(this.callbacks.sendCodexEvent(i),this.callbacks.sendEventResult(s,"responded"),this.clearActive())}}emitCodexEvent(e,s){this.activeEventId&&this.callbacks.sendCodexEvent({event_id:this.activeEventId,session_id:this.activeSessionId??"",thread_id:this.threadId??void 0,codex_event_type:"codex",codex_method:e,codex_sequence:++this.codexSequence,codex_payload:s,codex_at:new Date().toISOString()})}startComposing(){if(!this.activeSessionId||this.composingTimer)return;this.stopComposing();const e=this.activeSessionId,s={ttl_ms:this.composingTTL,...this.activeEventId?{ref_event_id:this.activeEventId}:{}};this.callbacks.sendSessionActivitySet(e,"composing",!0,s),this.composingTimer=setInterval(()=>{this.callbacks.sendSessionActivitySet(e,"composing",!0,s)},this.composingRefreshInterval),this.composingTTLClear=setTimeout(()=>{this.stopComposing()},this.composingTTL)}stopComposing(){this.composingTimer&&(clearInterval(this.composingTimer),this.composingTimer=null),this.composingTTLClear&&(clearTimeout(this.composingTTLClear),this.composingTTLClear=null),this.activeSessionId&&this.callbacks.sendSessionActivitySet(this.activeSessionId,"composing",!1)}async sendRequest(e,s,i=3e4){return new Promise((t,n)=>{if(!this.process?.stdin){n(new Error("Codex process not available"));return}const o=++this.requestId,d={jsonrpc:"2.0",id:o,method:e,params:s??{}},h=setTimeout(()=>{this.pendingRequests.delete(String(o)),n(new Error(`Request timeout: ${e}`))},i);this.pendingRequests.set(String(o),{resolve:u=>{u.error?n(new Error(`JSON-RPC error: ${u.error.message}`)):t(u.result)},reject:n,timer:h});const p=JSON.stringify(d);this.process.stdin.write(`${p}
|
|
5
5
|
`)})}sendNotification(e,s){if(!this.process?.stdin)return;const i={jsonrpc:"2.0",method:e,params:s??{}};this.process.stdin.write(`${JSON.stringify(i)}
|
|
6
6
|
`)}async initializeHandshake(){if(this.initialized)return;const e=j(),s=await this.sendRequest("initialize",{clientInfo:{name:"grix-connector",title:"Grix Connector",version:e},capabilities:{experimentalApi:!0}},15e3);r.info("codex-adapter",`Initialized: ${JSON.stringify(s)}`),this.sendNotification("initialized"),this.initialized=!0,await this.refreshCodexModelOptions(),this.fetchRateLimits()}fetchRateLimits(){Promise.resolve(this.sendRequest("account/rateLimits/read",void 0,1e4)).then(e=>{const s=this.parseRateLimitSnapshot(e);s&&(this.rateLimitSnapshot=s,r.info("codex-adapter",`Initial rate limits: primary=${s.primary.usedPercent.toFixed(1)}% secondary=${s.secondary.usedPercent.toFixed(1)}% credits=${s.credits.balance??"N/A"}`),this.callbacks.onRateLimitsUpdated?.(s))}).catch(e=>{r.warn("codex-adapter",`Failed to fetch rate limits: ${e instanceof Error?e.message:e}`)})}async runTurn(e,s,i,t){this.threadId||await this.createSession({cwd:this.cwd}),await this.ensureThreadResumed();const n=[];if(this.needsHistoryInjection&&t){this.needsHistoryInjection=!1;const h=await this.loadHistoryForInjection(t);for(const p of h)n.push(p);h.length>0&&r.info("codex-adapter",`Injected ${h.length} history items`)}if(e.contextMessages&&e.contextMessages.length>0){const h=e.contextMessages.map(p=>`[${p.senderId??"unknown"}]: ${p.content}`).join(`
|
|
7
7
|
`);n.push({type:"text",text:`Conversation context:
|
|
8
8
|
${h}
|
|
9
9
|
|
|
10
|
-
Latest user message:`,text_elements:[]})}n.push({type:"text",text:e.text,text_elements:[]});const o={threadId:this.threadId,approvalPolicy:this.approvalPolicy,input:n,model:this.model},d=Q(this.collaborationMode,this.model);d&&(o.collaborationMode=d),this.reasoningEffort&&(o.effort=this.reasoningEffort),await this.sendRequest("turn/start",o),s.emitDone({status:"completed"})}async ensureThreadResumed(){if(!(!this.threadId||!this.threadResumePending))try{const e=await this.sendRequest("thread/resume",{threadId:this.threadId,approvalPolicy:this.approvalPolicy,model:this.model,excludeTurns:!0,persistExtendedHistory:!1,...this.reasoningEffort?{effort:this.reasoningEffort}:{}});this.updateModelFromThreadResult(e),this.threadResumePending=!1}catch{this.threadId=null,this.needsHistoryInjection=!0,await this.startNewThread(),this.threadResumePending=!1}}async startNewThread(){try{const e=await this.sendRequest("thread/start",{approvalPolicy:this.approvalPolicy,model:this.model,cwd:this.cwd,experimentalRawEvents:!1,persistExtendedHistory:!1,...this.reasoningEffort?{effort:this.reasoningEffort}:{}}),s=K(e);if(!s)throw new Error("Codex thread id is missing in thread/start result");this.threadId=s,this.updateModelFromThreadResult(e),this.threadResumePending=!1,this.persistThreadId(s)}catch{throw new Error("Failed to start Codex thread")}}updateModelFromThreadResult(e){if(this.model)return;const s=Z(e);s&&(this.model=s,this.persistCodexContext())}persistThreadId(e){!this.bindingStore||!this.aibotSessionId||this.bindingStore.setCodexThreadId(this.aibotSessionId,e)}persistCodexContext(){!this.bindingStore||!this.aibotSessionId||this.bindingStore.setCodexContext(this.aibotSessionId,{modelId:this.model,modeId:v(this.collaborationMode)??void 0,reasoningEffort:this.reasoningEffort,sandboxMode:this.sandboxMode})}async loadHistoryForInjection(e){const s=this.callbacks.getConversationLog();if(!s)return[];try{const i=await s.readHistory(e,J),t=[];for(const n of i){const o=(n.content??"").slice(0,B);n.direction==="inbound"?t.push({type:"text",text:o,text_elements:[]}):n.direction==="outbound"&&t.push({type:"assistant",text:o,text_elements:[]})}return t}catch(i){return r.error("codex-adapter",`Failed to load history: ${i}`),[]}}async steerTurn(e){if(!this.threadId||!this.currentTurnId)throw new Error("No active turn to steer");await this.sendRequest("turn/steer",{threadId:this.threadId,expectedTurnId:this.currentTurnId,input:[{type:"text",text:e,text_elements:[]}]})}resumeAfterApproval(e,s,i){if(!this.threadId)return;const t=e.event_id?.trim()||`local_action_${e.action_id.trim()}`,n=x(s.session_id,s.sessionId)??this.activeSessionId??"";if(!n)return;const o=this.threadId,d=new S(o),h={adapterSessionId:o,text:""};this.activeEventId=t,this.activeSessionId=n,this.visibleAgentMessageIds.clear(),this.hiddenAgentMessageIds.clear(),this.codexSequence=0,this.runTurn(h,d,t,n).catch(p=>{r.error("codex-adapter",`Post-approval turn failed: ${p}`),this.callbacks.sendEventResult(t,"failed",p instanceof Error?p.message:String(p)),this.clearActive()}),this.resetIdleTimer(t)}clearActive(){this.stopComposing(),this.activeEventId=null,this.activeSessionId=null,this.currentTurnId=null,this.visibleAgentMessageIds.clear(),this.hiddenAgentMessageIds.clear(),this.agentMessagePhases.clear(),this.inFlightToolOps=0,this.clearIdleTimer(),this.pendingApprovals.clear()}resetIdleTimer(e){this.clearIdleTimer();const s=this.inFlightToolOps>0?W:U;this.idleTimer=setTimeout(()=>{this.activeEventId===e&&(r.error("codex-adapter",`Agent idle for ${s/1e3}s, declaring stuck: ${e}`),this.callbacks.sendEventResult(e,"failed",`agent idle for ${s/1e3}s`),this.clearActive(),this.emit("stuck"))},s)}isLongRunningToolItemType(e){if(!e)return!1;const s=e.toLowerCase();return s==="commandexecution"||s==="command_execution"||s==="item/commandexecution"||s==="filechange"||s==="file_change"||s==="item/filechange"}shouldSuppressAgentMessageDeltaDuringToolRun(e){if(this.inFlightToolOps<=0)return!1;if(!e)return!0;const s=this.agentMessagePhases.get(e);return s==="final_answer"?!1:(r.info("codex-adapter",`Suppressing agentMessage delta during tool run item_id=${e} phase=${s??"unknown"}`),!0)}clearIdleTimer(){this.idleTimer&&(clearTimeout(this.idleTimer),this.idleTimer=null)}rejectAllPending(e){for(const[,s]of this.pendingRequests)clearTimeout(s.timer),s.reject(new Error(`Request canceled: ${e}`));this.pendingRequests.clear(),this.pendingApprovals.clear()}}const V={allow:"accept","allow-once":"accept","allow-always":"acceptForSession",deny:"deny"};function Y(a){return a&&a.length>0?[...a]:["app-server"]}function l(a){if(typeof a!="string")return;const e=a.trim();return e||void 0}function K(a){if(!a||typeof a!="object")return null;const e=a,s=e.thread&&typeof e.thread=="object"?e.thread:void 0;return x(l(e.threadId),l(e.threadID),l(s?.id))??null}function Z(a){return!a||typeof a!="object"?null:l(a.model)??null}function v(a){const e=a?.trim().toLowerCase();return e==="default"||e==="plan"?e:null}function Q(a,e){const s=v(a),i=l(e);if(!(!s||!i))return{mode:s,settings:{model:i}}}function ee(a){if(!Array.isArray(a))return[];const e=[];for(const s of a){if(typeof s=="string"){const o=s.trim();o&&e.push(ie(o,_(o)));continue}if(!s||typeof s!="object")continue;const i=s,t=x(l(i.id),l(i.model_id),l(i.modelId),l(i.value));if(!t)continue;const n=x(l(i.displayName),l(i.display_name),l(i.name),l(i.label))??_(t);e.push({id:t,displayName:n,defaultReasoningEffort:l(i.defaultReasoningEffort)??null,supportedReasoningEfforts:
|
|
10
|
+
Latest user message:`,text_elements:[]})}n.push({type:"text",text:e.text,text_elements:[]});const o={threadId:this.threadId,approvalPolicy:this.approvalPolicy,input:n,model:this.model},d=Q(this.collaborationMode,this.model);d&&(o.collaborationMode=d),this.reasoningEffort&&(o.effort=this.reasoningEffort),await this.sendRequest("turn/start",o),s.emitDone({status:"completed"})}async ensureThreadResumed(){if(!(!this.threadId||!this.threadResumePending))try{const e=await this.sendRequest("thread/resume",{threadId:this.threadId,approvalPolicy:this.approvalPolicy,model:this.model,excludeTurns:!0,persistExtendedHistory:!1,...this.reasoningEffort?{effort:this.reasoningEffort}:{}});this.updateModelFromThreadResult(e),this.threadResumePending=!1}catch{this.threadId=null,this.needsHistoryInjection=!0,await this.startNewThread(),this.threadResumePending=!1}}async startNewThread(){try{const e=await this.sendRequest("thread/start",{approvalPolicy:this.approvalPolicy,model:this.model,cwd:this.cwd,experimentalRawEvents:!1,persistExtendedHistory:!1,...this.reasoningEffort?{effort:this.reasoningEffort}:{}}),s=K(e);if(!s)throw new Error("Codex thread id is missing in thread/start result");this.threadId=s,this.updateModelFromThreadResult(e),this.threadResumePending=!1,this.persistThreadId(s)}catch{throw new Error("Failed to start Codex thread")}}updateModelFromThreadResult(e){if(this.model)return;const s=Z(e);s&&(this.model=s,this.persistCodexContext())}persistThreadId(e){!this.bindingStore||!this.aibotSessionId||this.bindingStore.setCodexThreadId(this.aibotSessionId,e)}persistCodexContext(){!this.bindingStore||!this.aibotSessionId||this.bindingStore.setCodexContext(this.aibotSessionId,{modelId:this.model,modeId:v(this.collaborationMode)??void 0,reasoningEffort:this.reasoningEffort,sandboxMode:this.sandboxMode})}async loadHistoryForInjection(e){const s=this.callbacks.getConversationLog();if(!s)return[];try{const i=await s.readHistory(e,J),t=[];for(const n of i){const o=(n.content??"").slice(0,B);n.direction==="inbound"?t.push({type:"text",text:o,text_elements:[]}):n.direction==="outbound"&&t.push({type:"assistant",text:o,text_elements:[]})}return t}catch(i){return r.error("codex-adapter",`Failed to load history: ${i}`),[]}}async steerTurn(e){if(!this.threadId||!this.currentTurnId)throw new Error("No active turn to steer");await this.sendRequest("turn/steer",{threadId:this.threadId,expectedTurnId:this.currentTurnId,input:[{type:"text",text:e,text_elements:[]}]})}resumeAfterApproval(e,s,i){if(!this.threadId)return;const t=e.event_id?.trim()||`local_action_${e.action_id.trim()}`,n=x(s.session_id,s.sessionId)??this.activeSessionId??"";if(!n)return;const o=this.threadId,d=new S(o),h={adapterSessionId:o,text:""};this.activeEventId=t,this.activeSessionId=n,this.visibleAgentMessageIds.clear(),this.hiddenAgentMessageIds.clear(),this.codexSequence=0,this.runTurn(h,d,t,n).catch(p=>{r.error("codex-adapter",`Post-approval turn failed: ${p}`),this.callbacks.sendEventResult(t,"failed",p instanceof Error?p.message:String(p)),this.clearActive()}),this.resetIdleTimer(t)}clearActive(){const e=this.activeEventId;this.stopComposing(),this.activeEventId=null,this.activeSessionId=null,this.currentTurnId=null,this.visibleAgentMessageIds.clear(),this.hiddenAgentMessageIds.clear(),this.agentMessagePhases.clear(),this.inFlightToolOps=0,this.clearIdleTimer(),this.pendingApprovals.clear(),e&&this.emit("eventDone",e)}resetIdleTimer(e){this.clearIdleTimer();const s=this.inFlightToolOps>0?W:U;this.idleTimer=setTimeout(()=>{this.activeEventId===e&&(r.error("codex-adapter",`Agent idle for ${s/1e3}s, declaring stuck: ${e}`),this.callbacks.sendEventResult(e,"failed",`agent idle for ${s/1e3}s`),this.clearActive(),this.emit("stuck"))},s)}isLongRunningToolItemType(e){if(!e)return!1;const s=e.toLowerCase();return s==="commandexecution"||s==="command_execution"||s==="item/commandexecution"||s==="filechange"||s==="file_change"||s==="item/filechange"}shouldSuppressAgentMessageDeltaDuringToolRun(e){if(this.inFlightToolOps<=0)return!1;if(!e)return!0;const s=this.agentMessagePhases.get(e);return s==="final_answer"?!1:(r.info("codex-adapter",`Suppressing agentMessage delta during tool run item_id=${e} phase=${s??"unknown"}`),!0)}clearIdleTimer(){this.idleTimer&&(clearTimeout(this.idleTimer),this.idleTimer=null)}rejectAllPending(e){for(const[,s]of this.pendingRequests)clearTimeout(s.timer),s.reject(new Error(`Request canceled: ${e}`));this.pendingRequests.clear(),this.pendingApprovals.clear()}}const V={allow:"accept","allow-once":"accept","allow-always":"acceptForSession",deny:"deny"};function Y(a){return a&&a.length>0?[...a]:["app-server"]}function l(a){if(typeof a!="string")return;const e=a.trim();return e||void 0}function K(a){if(!a||typeof a!="object")return null;const e=a,s=e.thread&&typeof e.thread=="object"?e.thread:void 0;return x(l(e.threadId),l(e.threadID),l(s?.id))??null}function Z(a){return!a||typeof a!="object"?null:l(a.model)??null}function v(a){const e=a?.trim().toLowerCase();return e==="default"||e==="plan"?e:null}function Q(a,e){const s=v(a),i=l(e);if(!(!s||!i))return{mode:s,settings:{model:i}}}function ee(a){if(!Array.isArray(a))return[];const e=[];for(const s of a){if(typeof s=="string"){const o=s.trim();o&&e.push(ie(o,_(o)));continue}if(!s||typeof s!="object")continue;const i=s,t=x(l(i.id),l(i.model_id),l(i.modelId),l(i.value));if(!t)continue;const n=x(l(i.displayName),l(i.display_name),l(i.name),l(i.label))??_(t);e.push({id:t,displayName:n,defaultReasoningEffort:l(i.defaultReasoningEffort)??null,supportedReasoningEfforts:C(i.supportedReasoningEfforts),isDefault:i.isDefault===!0})}return e}function te(a){if(!a||typeof a!="object")return{models:[],nextCursor:null};const e=a,s=Array.isArray(e.data)?e.data:[],i=[];for(const t of s){if(!t||typeof t!="object")continue;const n=t;if(n.hidden===!0)continue;const o=x(l(n.id),l(n.model));if(!o)continue;const d=x(l(n.displayName),l(n.display_name),l(n.name))??_(o);i.push({id:o,displayName:d,defaultReasoningEffort:l(n.defaultReasoningEffort)??null,supportedReasoningEfforts:C(n.supportedReasoningEfforts),isDefault:n.isDefault===!0})}return{models:i,nextCursor:l(e.nextCursor)??null}}function se(a){const e=[],s=new Set;for(const i of a){const t=i.id.trim(),n=t.toLowerCase();!t||s.has(n)||(s.add(n),e.push({id:t,displayName:i.displayName.trim()||t,defaultReasoningEffort:i.defaultReasoningEffort,supportedReasoningEfforts:[...i.supportedReasoningEfforts],isDefault:i.isDefault}))}return e}function ie(a,e){return{id:a,displayName:e,defaultReasoningEffort:null,supportedReasoningEfforts:[],isDefault:!1}}function C(a){if(!Array.isArray(a))return[];const e=[];for(const s of a){if(typeof s=="string"){const t=s.trim();t&&e.push(t);continue}if(!s||typeof s!="object")continue;const i=l(s.reasoningEffort);i&&e.push(i)}return e}function _(a){return a.split(/[-_\s]+/).filter(Boolean).map(e=>/^(gpt|codex|o\d+)$/i.test(e)?e.toUpperCase():e.charAt(0).toUpperCase()+e.slice(1)).join(" ")}function x(...a){for(const e of a)if(e!=null){if(typeof e=="string"&&!e.trim())continue;return e}}function M(a){return a.trim().replace(/[^a-zA-Z0-9._:-]+/g,"_")}class S extends T{adapterSessionId;constructor(e){super(),this.adapterSessionId=e}emitDone(e){this.emit("done",e)}emitError(e){if(this.listenerCount("error")===0){r.warn("codex-adapter",`Prompt handle error (no listeners): ${e.message}`);return}this.emit("error",e)}async cancel(){}}export{ge as CodexAdapter};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import{EventEmitter as
|
|
2
|
-
`)){const R=
|
|
1
|
+
import{EventEmitter as I}from"node:events";import{resolveCommandPath as k,spawnCommand as w,killProcessGroup as g}from"../../core/runtime/spawn.js";import{OpenCodeTransport as b}from"./opencode-transport.js";import{log as n}from"../../core/log/index.js";import{splitTextForAibotProtocol as T}from"../../core/protocol/index.js";class E extends I{adapterSessionId;constructor(e){super(),this.adapterSessionId=e}emitError(e){if(this.listenerCount("error")===0){n.warn("opencode-adapter",`Prompt handle error (no listeners): ${e.message}`);return}this.emit("error",e)}async cancel(){}}const y=200,_=2e3,$=12e4,C=6e5,S=3e4,P="127.0.0.1",A=0,O=1e3;class H extends I{type="opencode";config;callbacks;options;process=null;transport=new b;alive=!1;stopped=!1;sessions=new Map;activeRun=null;completedEvents=new Set;clientMsgSeq=0;idleTimer=null;composingSessionId=null;composingInterval=null;pendingPermissions=new Map;permissionHandler=null;constructor(e,t,s){super(),this.config=e,this.callbacks=t,this.options=s??{}}async start(){const e=this.options.hostname??P,t=this.options.port??A,s=await this.spawnAndWait(e,t),i=this.resolveCwd();await this.transport.connect(s,i),this.transport.on("event",o=>this.handleSseEvent(o)),n.info("opencode-adapter",`Ready (pid=${this.process?.pid}, url=${s})`)}async stop(){if(this.stopped=!0,this.alive=!1,this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.transport.disconnect(),this.process){const e=this.process;try{g(e,"SIGTERM")}catch{}const t=setTimeout(()=>{try{g(e,"SIGKILL")}catch{}},5e3);e.on("exit",()=>clearTimeout(t)),this.process=null}}isAlive(){return this.alive}async createSession(e){const t=e.cwd??this.resolveCwd(),s=await this.transport.createSession({title:`grix-${Date.now()}`});return this.sessions.set(s.id,{ocSessionId:s.id,cwd:t}),n.info("opencode-adapter",`Created OC session ${s.id} for cwd=${t}`),s.id}async resumeSession(e,t){const s=this.sessions.get(e);if(s?.ocSessionId)try{await this.transport.getSession(s.ocSessionId)}catch{n.warn("opencode-adapter",`OC session ${s.ocSessionId} gone, will create new on next prompt`),s.ocSessionId=""}}async destroySession(e){try{await this.transport.deleteSession(e)}catch{}this.sessions.delete(e)}sendPrompt(e){const t=new E(e.adapterSessionId);return this.sendOcPrompt(e.adapterSessionId,this.buildPromptText(e)).catch(s=>{t.emitError(s instanceof Error?s:new Error(String(s)))}),t}async cancel(e){if(this.activeRun)try{await this.transport.abortSession(this.activeRun.ocSessionId)}catch{}}deliverInboundEvent(e){const{event_id:t,session_id:s,content:i}=e;if(this.completedEvents.has(t)){n.info("opencode-adapter",`Dropping duplicate event ${t}`),this.callbacks.sendEventAck(t,s),this.callbacks.sendEventResult(t,"responded");return}if(!this.alive){n.warn("opencode-adapter",`Dropping event ${t}: process not alive`),this.callbacks.sendEventAck(t,s),this.callbacks.sendEventResult(t,"failed","Agent process not running");return}this.activeRun&&this.activeRun.eventId!==t&&(n.info("opencode-adapter",`steer: ${this.activeRun.eventId} -> ${t}`),this.flushTextBuffer(),this.callbacks.sendEventResult(this.activeRun.eventId,"canceled","steered to new event"),this.clearRun()),n.info("opencode-adapter",`prompt: event=${t} session=${s}`),this.callbacks.sendEventAck(t,s),this.startRun(t,s),this.startComposing(s),this.resetIdleTimer();const o=this.buildPromptTextFromEvent(e);this.sendOcPrompt(s,o).catch(a=>{n.error("opencode-adapter",`prompt_async failed: ${a}`),this.finishRun("failed",String(a))})}deliverStopEvent(e,t){this.activeRun&&this.activeRun.eventId===e&&(n.info("opencode-adapter",`stop: event=${e}`),this.transport.abortSession(this.activeRun.ocSessionId).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]:[]}clearActiveEventForShutdown(){this.stopIdleTimer(),this.stopTextFlush(),this.activeRun=null}getMcpConfig(){return null}async handleLocalAction(e){const{action_type:t}=e;return t==="exec_approve"||t==="exec_reject"||t==="permission_approve"||t==="permission_reject"?this.handlePermissionAction(e):{handled:!1,kind:""}}bindSession(e,t){if(n.info("opencode-adapter",`bindSession: ${e} \u2192 ${t}`),!this.sessions.has(e))this.sessions.set(e,{ocSessionId:"",cwd:t});else{const s=this.sessions.get(e);s.cwd=t}}async spawnAndWait(e,t){return new Promise((s,i)=>{const o=this.config.command||"opencode",c=[...this.config.args??["serve"],`--hostname=${e}`,`--port=${t}`],l={...process.env,...this.config.env},p={};if(this.options.model){const[r,...h]=this.options.model.split("/");r&&h.length>0&&(p.agents={coder:{model:h.join("/"),maxTokens:16e3}})}this.options.permissionPolicy==="fullAuto"&&(p.permission={edit:"allow",bash:"allow",webfetch:"allow",doom_loop:"allow",external_directory:"allow"}),Object.keys(p).length>0&&(l.OPENCODE_CONFIG_CONTENT=JSON.stringify(p));const f=this.resolveCwd(),m=k(o,typeof l.PATH=="string"?l.PATH:void 0);n.info("opencode-adapter",`Spawning: ${m} ${c.join(" ")} (cwd=${f})`),this.process=w(m,c,{env:l,cwd:f}).process;let v="",d=!1;const u=setTimeout(()=>{d||(d=!0,i(new Error(`opencode serve did not start after ${S/1e3}s`)))},S);this.process.stdout?.on("data",r=>{if(v+=r.toString(),!d)for(const h of v.split(`
|
|
2
|
+
`)){const R=h.match(/opencode server listening on (https?:\/\/[^\s]+)/);if(R){clearTimeout(u),d=!0,this.alive=!0,s(R[1]);return}}}),this.process.stderr?.on("data",r=>{const h=r.toString().trim();h&&n.info("opencode-adapter",`[stderr] ${h}`)}),this.process.on("error",r=>{n.error("opencode-adapter",`Spawn error: ${r.message}`),clearTimeout(u),this.alive=!1,this.transport.disconnect(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed",`Spawn error: ${r.message}`),this.clearRun()),d?this.stopped||this.emit("exit",1):(d=!0,i(r))}),this.process.on("exit",r=>{n.info("opencode-adapter",`Process exited (code=${r})`),clearTimeout(u),this.alive=!1,this.transport.disconnect(),this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed",`Process exited (code=${r})`),this.clearRun()),d?this.stopped||this.emit("exit",r??1):(d=!0,i(new Error(`opencode serve exited with code ${r}`)))})})}async ensureOcSession(e){const t=this.sessions.get(e);if(t?.ocSessionId)try{return await this.transport.getSession(t.ocSessionId),t.ocSessionId}catch{n.warn("opencode-adapter",`OC session ${t.ocSessionId} gone, creating new`),t.ocSessionId=""}const s=t?.cwd??this.resolveCwd(),i=await this.transport.createSession({title:`grix-${e.slice(-8)}`});return this.sessions.set(e,{ocSessionId:i.id,cwd:s}),n.info("opencode-adapter",`Created OC session ${i.id} for aibot=${e}`),i.id}async sendOcPrompt(e,t){const s=await this.ensureOcSession(e);this.activeRun&&(this.activeRun.ocSessionId=s),await this.transport.sendPromptAsync(s,{parts:[{type:"text",text:t}]})}handleSseEvent(e){if(!this.stopped)switch(this.resetIdleTimer(),e.type){case"message.part.updated":{if(!this.activeRun)break;const t=e.part,s=e.delta;t.type==="text"?s?this.appendText(s):t.text&&this.appendText(t.text):t.type==="reasoning"?s&&this.callbacks.sendThinking(this.activeRun.eventId,this.activeRun.sessionId,s):t.type==="tool"&&this.handleToolPartUpdate(t,s);break}case"message.updated":{if(!this.activeRun)break;const t=e.info;if(t.role==="assistant"){const s=t;s.error&&n.warn("opencode-adapter",`Message error: ${JSON.stringify(s.error)}`)}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";n.error("opencode-adapter",`Session error: ${s}`),this.flushTextBuffer(),this.finishRun("failed",s);break}case"permission.updated":{this.handlePermission(e.permission);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":{n.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:break}}handleToolPartUpdate(e,t){if(!this.activeRun)return;const s=e.state;switch(s.status){case"pending":case"running":{this.flushTextBuffer();const i=JSON.stringify(s.input??{});this.callbacks.sendToolUse(this.activeRun.eventId,this.activeRun.sessionId,e.tool,i);break}case"completed":{const i=s.output??"";this.callbacks.sendToolResult(this.activeRun.eventId,this.activeRun.sessionId,e.tool,i);break}case"error":{const i=s.error??"unknown tool error";this.callbacks.sendToolResult(this.activeRun.eventId,this.activeRun.sessionId,e.tool,`Error: ${i}`);break}}}handlePermission(e){if(!this.activeRun)return;const{eventId:t,sessionId:s,ocSessionId:i}=this.activeRun;if(this.options.permissionPolicy==="fullAuto"){this.transport.respondPermission(i,e.id,"always").catch(a=>{n.warn("opencode-adapter",`Auto-approve failed: ${a}`)});return}this.stopIdleTimer(),this.pendingPermissions.set(t,{permissionId:e.id,ocSessionId:i});const o=JSON.stringify(e.metadata??{});this.callbacks.sendToolUse(t,s,e.type,o)}handlePermissionAction(e){if(!this.activeRun)return{handled:!1,kind:""};const t=this.pendingPermissions.get(this.activeRun.eventId);if(!t)return{handled:!1,kind:""};const{permissionId:s,ocSessionId:i}=t,a=e.action_type==="exec_approve"||e.action_type==="permission_approve"?"once":"reject";return this.pendingPermissions.delete(this.activeRun.eventId),this.transport.respondPermission(i,s,a).then(()=>{n.info("opencode-adapter",`Permission ${a}: ${s}`),this.resetIdleTimer()}).catch(c=>{n.warn("opencode-adapter",`Permission response failed: ${c}`)}),{handled:!0,kind:"permission"}}startRun(e,t){this.activeRun={eventId:e,sessionId:t,ocSessionId:"",chunkSeq:0,clientMsgId:`oc_${++this.clientMsgSeq}_${Date.now()}`,textBuffer:"",flushTimer:null}}finishRun(e,t){const s=this.activeRun;if(!s)return;if(this.completedEvents.add(s.eventId),this.completedEvents.size>O){const c=[...this.completedEvents].slice(-500);this.completedEvents=new Set(c)}this.activeRun=null,this.stopComposing(),this.stopIdleTimer();const i=++s.chunkSeq,o=s.clientMsgId;e==="failed"&&t&&this.callbacks.sendRunError(s.eventId,s.sessionId,t);const a=()=>{this.callbacks.sendFinalStreamChunkReliable?this.callbacks.sendFinalStreamChunkReliable(s.eventId,s.sessionId,i,o).then(()=>{this.callbacks.sendEventResult(s.eventId,e,t)}).catch(()=>{this.callbacks.sendStreamChunk(s.eventId,s.sessionId,"",i,!0,o),this.callbacks.sendEventResult(s.eventId,e,t)}):(this.callbacks.sendStreamChunk(s.eventId,s.sessionId,"",i,!0,o),this.callbacks.sendEventResult(s.eventId,e,t))};if(s.textBuffer){this.stopTextFlush();for(const c of T(s.textBuffer))s.chunkSeq++,this.callbacks.sendStreamChunk(s.eventId,s.sessionId,c,s.chunkSeq,!1,s.clientMsgId);s.textBuffer=""}a(),this.emit("eventDone",s.eventId)}clearRun(){this.activeRun?.flushTimer&&clearTimeout(this.activeRun.flushTimer);const e=this.activeRun?.eventId;this.activeRun=null,e&&this.emit("eventDone",e)}appendText(e){if(this.activeRun){if(this.activeRun.textBuffer+=e,this.activeRun.textBuffer.length>=_){this.flushTextBuffer();return}this.scheduleTextFlush()}}scheduleTextFlush(){!this.activeRun||this.activeRun.flushTimer||(this.activeRun.flushTimer=setTimeout(()=>{this.activeRun&&(this.activeRun.flushTimer=null),this.flushTextBuffer()},y))}flushTextBuffer(){if(this.stopTextFlush(),!(!this.activeRun||!this.activeRun.textBuffer)){for(const e of T(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){this.stopComposing(),this.composingSessionId=e,this.callbacks.sendSessionComposing(e,!0),this.composingInterval=setInterval(()=>{this.composingSessionId&&this.callbacks.sendSessionComposing(this.composingSessionId,!0)},3e4)}stopComposing(){this.composingInterval&&(clearInterval(this.composingInterval),this.composingInterval=null),this.composingSessionId&&(this.callbacks.sendSessionComposing(this.composingSessionId,!1),this.composingSessionId=null)}resetIdleTimer(){if(this.stopIdleTimer(),this.stopped||!this.activeRun)return;const e=this.pendingPermissions.has(this.activeRun.eventId)?C:$;this.idleTimer=setTimeout(()=>{n.error("opencode-adapter","Idle timeout \u2014 emitting exit for respawn"),this.flushTextBuffer(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed","idle timeout"),this.clearRun()),this.emit("exit",-1)},e)}stopIdleTimer(){this.idleTimer&&(clearTimeout(this.idleTimer),this.idleTimer=null)}resolveCwd(){const e=(this.config.options??{}).cwd;return typeof e=="string"&&e?e:process.cwd()}buildPromptText(e){let t=e.text;return e.contextMessages&&e.contextMessages.length>0&&(t=e.contextMessages.map(i=>`[context] ${i.senderId}: ${i.content}`).join(`
|
|
3
3
|
`)+`
|
|
4
4
|
|
|
5
5
|
`+t),t}buildPromptTextFromEvent(e){let t=e.content||"";if(e.context_messages_json)try{const s=JSON.parse(e.context_messages_json);Array.isArray(s)&&s.length>0&&(t=s.map(o=>`[context] ${o.sender_id??"unknown"}: ${o.content}`).join(`
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import{EventEmitter as u}from"node:events";import{homedir as d}from"node:os";import{join as v}from"node:path";import{resolveCommandPath as R,spawnCommand as I,killProcessGroup as p}from"../../core/runtime/spawn.js";import{OpenHumanTransport as g,readBearerToken as T}from"./openhuman-transport.js";import{log as n}from"../../core/log/index.js";import{splitTextForAibotProtocol as k}from"../../core/protocol/index.js";class _ extends u{adapterSessionId;constructor(t){super(),this.adapterSessionId=t}emitError(t){if(this.listenerCount("error")===0){n.warn("openhuman-adapter",`Prompt handle error (no listeners): ${t.message}`);return}this.emit("error",t)}async cancel(){}}const x=200,S=2e3,b=12e4,w=500,f=3e4,E=7788,$="127.0.0.1";class q extends u{type="openhuman";config;callbacks;options;process=null;transport=new g;alive=!1;stopped=!1;clientId;activeRun=null;completedEvents=new Set;clientMsgSeq=0;idleTimer=null;composingSessionId=null;composingInterval=null;constructor(t,e,s){super(),this.config=t,this.callbacks=e,this.options=s??{},this.clientId=`grix-connector-${Date.now()}`}async start(){const t=this.options.host??$,e=this.options.port??E;this.spawnProcess(t,e),await this.waitForServerReady(t,e);const s=this.resolveWorkspaceDir(),i=await T(s),a=`http://${t}:${e}`;if(await this.transport.connect(a,i),this.transport.on("event",o=>this.handleSocketEvent(o)),this.options.sessionToken)try{await this.transport.storeSession({token:this.options.sessionToken}),n.info("openhuman-adapter","Session token stored")}catch(o){n.warn("openhuman-adapter",`Failed to store session token: ${o}`)}this.transport.socketId&&(this.clientId=this.transport.socketId),n.info("openhuman-adapter",`Ready (pid=${this.process?.pid}, clientId=${this.clientId})`)}async stop(){if(this.stopped=!0,this.alive=!1,this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.transport.disconnect(),this.process){const t=this.process;try{p(t,"SIGTERM")}catch{}const e=setTimeout(()=>{try{p(t,"SIGKILL")}catch{}},5e3);t.on("exit",()=>clearTimeout(e)),this.process=null}}isAlive(){return this.alive}async createSession(t){return this.clientId}async resumeSession(t,e){}async destroySession(t){}sendPrompt(t){const e=new _(t.adapterSessionId),s=this.buildPromptText(t);return this.doWebChat(t.adapterSessionId,s).then(i=>{this.activeRun&&(this.activeRun.requestId=i.request_id)}).catch(i=>{e.emitError(i instanceof Error?i:new Error(String(i)))}),e}async cancel(t){if(this.activeRun)try{await this.transport.webCancel({client_id:this.clientId,thread_id:this.activeRun.threadId})}catch{}}deliverInboundEvent(t){const{event_id:e,session_id:s,content:i}=t,a=this.buildPromptTextFromEvent(t);if(this.completedEvents.has(e)){n.info("openhuman-adapter",`Dropping duplicate event ${e}`),this.callbacks.sendEventAck(e,s),this.callbacks.sendEventResult(e,"responded");return}if(!this.alive){n.warn("openhuman-adapter",`Dropping event ${e}: process not alive`),this.callbacks.sendEventAck(e,s),this.callbacks.sendEventResult(e,"failed","Agent process not running");return}this.activeRun&&this.activeRun.eventId!==e&&(n.info("openhuman-adapter",`steer: ${this.activeRun.eventId} -> ${e}`),this.flushTextBuffer(),this.callbacks.sendEventResult(this.activeRun.eventId,"canceled","steered to new event"),this.clearRun()),n.info("openhuman-adapter",`prompt: event=${e} session=${s}`),this.callbacks.sendEventAck(e,s),this.startRun(e,s,s),this.startComposing(s,e),this.resetIdleTimer(),this.doWebChat(s,a).then(o=>{this.activeRun&&this.activeRun.eventId===e&&(this.activeRun.requestId=o.request_id,o.accepted||(n.warn("openhuman-adapter",`web_chat not accepted: ${o.request_id}`),this.finishRun("failed","Chat request not accepted")))}).catch(o=>{n.error("openhuman-adapter",`web_chat failed: ${o}`),this.finishRun("failed",String(o))})}deliverStopEvent(t,e){this.activeRun&&this.activeRun.eventId===t&&(n.info("openhuman-adapter",`stop: event=${t}`),this.transport.webCancel({client_id:this.clientId,thread_id:this.activeRun.threadId}).catch(()=>{}),this.flushTextBuffer(),this.finishRun("canceled","stopped by user"))}setPermissionHandler(t){}async ping(t){return this.transport.healthCheck()}getStatus(){return{alive:this.alive,busy:this.activeRun!==null,sessions:this.activeRun?1:0}}getActiveEventIds(){return this.activeRun?[this.activeRun.eventId]:[]}clearActiveEventForShutdown(){this.stopIdleTimer(),this.stopTextFlush(),this.activeRun=null}getMcpConfig(){return null}spawnProcess(t,e){const s=this.config.command||"openhuman-core",i=R(s,typeof process.env.PATH=="string"?process.env.PATH:void 0),a=this.config.args??[],c=a.includes("run")||a.includes("serve")?a:["run","--host",t,"--port",String(e),...a],h={...process.env,...this.config.env};n.info("openhuman-adapter",`Spawning: ${i} ${c.join(" ")}`);try{this.process=I(i,c,{env:h,cwd:this.resolveCwd()}).process}catch(r){throw n.error("openhuman-adapter",`Spawn threw: ${r}`),this.alive=!1,r}this.process.on("error",r=>{n.error("openhuman-adapter",`Spawn error: ${r.message}`),this.alive=!1,this.transport.disconnect(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed",`Spawn error: ${r.message}`),this.clearRun()),this.stopped||this.emit("exit",1)}),this.process.on("exit",r=>{n.info("openhuman-adapter",`Process exited (code=${r})`),this.alive=!1,this.transport.disconnect(),this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed",`Process exited (code=${r})`),this.clearRun()),this.stopped||this.emit("exit",r??1)}),this.process.stderr?.on("data",r=>{const l=r.toString().trim();l&&n.info("openhuman-adapter",`[stderr] ${l}`)}),this.alive=!0}async waitForServerReady(t,e){const s=`http://${t}:${e}/health`,i=Date.now()+f;for(;Date.now()<i;){try{if((await fetch(s,{signal:AbortSignal.timeout(2e3)})).ok)return}catch{}await new Promise(a=>setTimeout(a,w))}throw new Error(`openhuman-core did not become ready at ${t}:${e} after ${f/1e3}s`)}async doWebChat(t,e){return this.transport.webChat({client_id:this.clientId,thread_id:t,message:e})}handleSocketEvent(t){if(this.stopped||!this.activeRun||t.request_id&&this.activeRun.requestId&&t.request_id!==this.activeRun.requestId)return;this.resetIdleTimer();const e=t.event;switch(e){case"text_delta":{t.delta&&t.delta_kind==="text"&&this.appendText(t.delta);break}case"thinking_delta":{t.delta&&t.delta_kind==="thinking"&&this.activeRun&&this.callbacks.sendThinking(this.activeRun.eventId,this.activeRun.sessionId,t.delta);break}case"tool_call":{if(this.flushTextBuffer(),this.activeRun&&t.tool_name){const s=typeof t.args=="string"?t.args:JSON.stringify(t.args??{});this.callbacks.sendToolUse(this.activeRun.eventId,this.activeRun.sessionId,t.tool_name,s)}break}case"tool_result":{if(this.activeRun&&t.tool_name){const s=t.output??"";this.callbacks.sendToolResult(this.activeRun.eventId,this.activeRun.sessionId,t.tool_name,s)}break}case"chat_done":{n.info("openhuman-adapter",`chat_done request=${t.request_id}`),this.activeRun&&this.activeRun.textBuffer.length===0&&t.full_response&&(this.activeRun.textBuffer=t.full_response),this.flushTextBuffer(),this.finishRun("responded");break}case"chat_error":{if(n.error("openhuman-adapter",`chat_error: ${t.error_type} ${t.message}`),this.flushTextBuffer(),this.activeRun){const s=t.message??t.error_type??"unknown error";this.callbacks.sendRunError(this.activeRun.eventId,this.activeRun.sessionId,s)}this.finishRun("failed",t.message);break}case"chat_segment":case"inference_start":case"iteration_start":case"chat_accepted":break;default:{n.debug("openhuman-adapter",`Unhandled event: ${e}`);break}}}startRun(t,e,s){this.activeRun={eventId:t,sessionId:e,requestId:"",threadId:s,chunkSeq:0,clientMsgId:`oh_${++this.clientMsgSeq}_${Date.now()}`,textBuffer:"",flushTimer:null}}finishRun(t,e){const s=this.activeRun;if(!s)return;if(this.completedEvents.add(s.eventId),this.completedEvents.size>1e3){const o=this.completedEvents.values();for(let h=0;h<500;h++)o.next();const c=[...this.completedEvents].slice(-500);this.completedEvents=new Set(c)}this.activeRun=null,this.stopComposing(),this.stopIdleTimer();const i=++s.chunkSeq,a=s.clientMsgId;t==="failed"&&e&&this.callbacks.sendRunError(s.eventId,s.sessionId,e),this.callbacks.sendFinalStreamChunkReliable?this.callbacks.sendFinalStreamChunkReliable(s.eventId,s.sessionId,i,a).then(()=>{this.callbacks.sendEventResult(s.eventId,t,e)}).catch(()=>{this.callbacks.sendStreamChunk(s.eventId,s.sessionId,"",i,!0,a),this.callbacks.sendEventResult(s.eventId,t,e)}):(this.callbacks.sendStreamChunk(s.eventId,s.sessionId,"",i,!0,a),this.callbacks.sendEventResult(s.eventId,t,e))}clearRun(){this.activeRun?.flushTimer&&clearTimeout(this.activeRun.flushTimer),this.activeRun=null}appendText(t){if(this.activeRun){if(this.activeRun.textBuffer+=t,this.activeRun.textBuffer.length>=S){this.flushTextBuffer();return}this.scheduleTextFlush()}}scheduleTextFlush(){!this.activeRun||this.activeRun.flushTimer||(this.activeRun.flushTimer=setTimeout(()=>{this.activeRun&&(this.activeRun.flushTimer=null),this.flushTextBuffer()},x))}flushTextBuffer(){if(this.stopTextFlush(),!(!this.activeRun||!this.activeRun.textBuffer)){for(const t of k(this.activeRun.textBuffer))this.activeRun.chunkSeq++,this.callbacks.sendStreamChunk(this.activeRun.eventId,this.activeRun.sessionId,t,this.activeRun.chunkSeq,!1,this.activeRun.clientMsgId);this.activeRun.textBuffer=""}}stopTextFlush(){this.activeRun?.flushTimer&&(clearTimeout(this.activeRun.flushTimer),this.activeRun.flushTimer=null)}startComposing(t,e){this.stopComposing(),this.composingSessionId=t,this.callbacks.sendSessionComposing(t,!0),this.composingInterval=setInterval(()=>{this.composingSessionId&&this.callbacks.sendSessionComposing(this.composingSessionId,!0)},3e4)}stopComposing(){this.composingInterval&&(clearInterval(this.composingInterval),this.composingInterval=null),this.composingSessionId&&(this.callbacks.sendSessionComposing(this.composingSessionId,!1),this.composingSessionId=null)}resetIdleTimer(){this.stopIdleTimer(),!(this.stopped||!this.activeRun)&&(this.idleTimer=setTimeout(()=>{n.error("openhuman-adapter","Idle timeout \u2014 emitting exit for respawn"),this.flushTextBuffer(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed","idle timeout"),this.clearRun()),this.emit("exit",-1)},b))}stopIdleTimer(){this.idleTimer&&(clearTimeout(this.idleTimer),this.idleTimer=null)}resolveWorkspaceDir(){return this.options.workspaceDir?this.options.workspaceDir.replace(/^~/,d()):v(d(),".openhuman")}resolveCwd(){const t=(this.config.options??{}).cwd;return typeof t=="string"&&t?t:process.cwd()}buildPromptText(t){let e=t.text;return t.contextMessages&&t.contextMessages.length>0&&(e=t.contextMessages.map(i=>`[context] ${i.senderId}: ${i.content}`).join(`
|
|
1
|
+
import{EventEmitter as u}from"node:events";import{homedir as d}from"node:os";import{join as v}from"node:path";import{resolveCommandPath as R,spawnCommand as I,killProcessGroup as p}from"../../core/runtime/spawn.js";import{OpenHumanTransport as g,readBearerToken as T}from"./openhuman-transport.js";import{log as n}from"../../core/log/index.js";import{splitTextForAibotProtocol as k}from"../../core/protocol/index.js";class _ extends u{adapterSessionId;constructor(t){super(),this.adapterSessionId=t}emitError(t){if(this.listenerCount("error")===0){n.warn("openhuman-adapter",`Prompt handle error (no listeners): ${t.message}`);return}this.emit("error",t)}async cancel(){}}const x=200,S=2e3,b=12e4,w=500,f=3e4,E=7788,$="127.0.0.1";class D extends u{type="openhuman";config;callbacks;options;process=null;transport=new g;alive=!1;stopped=!1;clientId;activeRun=null;completedEvents=new Set;clientMsgSeq=0;idleTimer=null;composingSessionId=null;composingInterval=null;constructor(t,e,s){super(),this.config=t,this.callbacks=e,this.options=s??{},this.clientId=`grix-connector-${Date.now()}`}async start(){const t=this.options.host??$,e=this.options.port??E;this.spawnProcess(t,e),await this.waitForServerReady(t,e);const s=this.resolveWorkspaceDir(),i=await T(s),a=`http://${t}:${e}`;if(await this.transport.connect(a,i),this.transport.on("event",o=>this.handleSocketEvent(o)),this.options.sessionToken)try{await this.transport.storeSession({token:this.options.sessionToken}),n.info("openhuman-adapter","Session token stored")}catch(o){n.warn("openhuman-adapter",`Failed to store session token: ${o}`)}this.transport.socketId&&(this.clientId=this.transport.socketId),n.info("openhuman-adapter",`Ready (pid=${this.process?.pid}, clientId=${this.clientId})`)}async stop(){if(this.stopped=!0,this.alive=!1,this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.transport.disconnect(),this.process){const t=this.process;try{p(t,"SIGTERM")}catch{}const e=setTimeout(()=>{try{p(t,"SIGKILL")}catch{}},5e3);t.on("exit",()=>clearTimeout(e)),this.process=null}}isAlive(){return this.alive}async createSession(t){return this.clientId}async resumeSession(t,e){}async destroySession(t){}sendPrompt(t){const e=new _(t.adapterSessionId),s=this.buildPromptText(t);return this.doWebChat(t.adapterSessionId,s).then(i=>{this.activeRun&&(this.activeRun.requestId=i.request_id)}).catch(i=>{e.emitError(i instanceof Error?i:new Error(String(i)))}),e}async cancel(t){if(this.activeRun)try{await this.transport.webCancel({client_id:this.clientId,thread_id:this.activeRun.threadId})}catch{}}deliverInboundEvent(t){const{event_id:e,session_id:s,content:i}=t,a=this.buildPromptTextFromEvent(t);if(this.completedEvents.has(e)){n.info("openhuman-adapter",`Dropping duplicate event ${e}`),this.callbacks.sendEventAck(e,s),this.callbacks.sendEventResult(e,"responded");return}if(!this.alive){n.warn("openhuman-adapter",`Dropping event ${e}: process not alive`),this.callbacks.sendEventAck(e,s),this.callbacks.sendEventResult(e,"failed","Agent process not running");return}this.activeRun&&this.activeRun.eventId!==e&&(n.info("openhuman-adapter",`steer: ${this.activeRun.eventId} -> ${e}`),this.flushTextBuffer(),this.callbacks.sendEventResult(this.activeRun.eventId,"canceled","steered to new event"),this.clearRun()),n.info("openhuman-adapter",`prompt: event=${e} session=${s}`),this.callbacks.sendEventAck(e,s),this.startRun(e,s,s),this.startComposing(s,e),this.resetIdleTimer(),this.doWebChat(s,a).then(o=>{this.activeRun&&this.activeRun.eventId===e&&(this.activeRun.requestId=o.request_id,o.accepted||(n.warn("openhuman-adapter",`web_chat not accepted: ${o.request_id}`),this.finishRun("failed","Chat request not accepted")))}).catch(o=>{n.error("openhuman-adapter",`web_chat failed: ${o}`),this.finishRun("failed",String(o))})}deliverStopEvent(t,e){this.activeRun&&this.activeRun.eventId===t&&(n.info("openhuman-adapter",`stop: event=${t}`),this.transport.webCancel({client_id:this.clientId,thread_id:this.activeRun.threadId}).catch(()=>{}),this.flushTextBuffer(),this.finishRun("canceled","stopped by user"))}setPermissionHandler(t){}async ping(t){return this.transport.healthCheck()}getStatus(){return{alive:this.alive,busy:this.activeRun!==null,sessions:this.activeRun?1:0}}getActiveEventIds(){return this.activeRun?[this.activeRun.eventId]:[]}clearActiveEventForShutdown(){this.stopIdleTimer(),this.stopTextFlush(),this.activeRun=null}getMcpConfig(){return null}spawnProcess(t,e){const s=this.config.command||"openhuman-core",i=R(s,typeof process.env.PATH=="string"?process.env.PATH:void 0),a=this.config.args??[],c=a.includes("run")||a.includes("serve")?a:["run","--host",t,"--port",String(e),...a],h={...process.env,...this.config.env};n.info("openhuman-adapter",`Spawning: ${i} ${c.join(" ")}`);try{this.process=I(i,c,{env:h,cwd:this.resolveCwd()}).process}catch(r){throw n.error("openhuman-adapter",`Spawn threw: ${r}`),this.alive=!1,r}this.process.on("error",r=>{n.error("openhuman-adapter",`Spawn error: ${r.message}`),this.alive=!1,this.transport.disconnect(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed",`Spawn error: ${r.message}`),this.clearRun()),this.stopped||this.emit("exit",1)}),this.process.on("exit",r=>{n.info("openhuman-adapter",`Process exited (code=${r})`),this.alive=!1,this.transport.disconnect(),this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed",`Process exited (code=${r})`),this.clearRun()),this.stopped||this.emit("exit",r??1)}),this.process.stderr?.on("data",r=>{const l=r.toString().trim();l&&n.info("openhuman-adapter",`[stderr] ${l}`)}),this.alive=!0}async waitForServerReady(t,e){const s=`http://${t}:${e}/health`,i=Date.now()+f;for(;Date.now()<i;){try{if((await fetch(s,{signal:AbortSignal.timeout(2e3)})).ok)return}catch{}await new Promise(a=>setTimeout(a,w))}throw new Error(`openhuman-core did not become ready at ${t}:${e} after ${f/1e3}s`)}async doWebChat(t,e){return this.transport.webChat({client_id:this.clientId,thread_id:t,message:e})}handleSocketEvent(t){if(this.stopped||!this.activeRun||t.request_id&&this.activeRun.requestId&&t.request_id!==this.activeRun.requestId)return;this.resetIdleTimer();const e=t.event;switch(e){case"text_delta":{t.delta&&t.delta_kind==="text"&&this.appendText(t.delta);break}case"thinking_delta":{t.delta&&t.delta_kind==="thinking"&&this.activeRun&&this.callbacks.sendThinking(this.activeRun.eventId,this.activeRun.sessionId,t.delta);break}case"tool_call":{if(this.flushTextBuffer(),this.activeRun&&t.tool_name){const s=typeof t.args=="string"?t.args:JSON.stringify(t.args??{});this.callbacks.sendToolUse(this.activeRun.eventId,this.activeRun.sessionId,t.tool_name,s)}break}case"tool_result":{if(this.activeRun&&t.tool_name){const s=t.output??"";this.callbacks.sendToolResult(this.activeRun.eventId,this.activeRun.sessionId,t.tool_name,s)}break}case"chat_done":{n.info("openhuman-adapter",`chat_done request=${t.request_id}`),this.activeRun&&this.activeRun.textBuffer.length===0&&t.full_response&&(this.activeRun.textBuffer=t.full_response),this.flushTextBuffer(),this.finishRun("responded");break}case"chat_error":{if(n.error("openhuman-adapter",`chat_error: ${t.error_type} ${t.message}`),this.flushTextBuffer(),this.activeRun){const s=t.message??t.error_type??"unknown error";this.callbacks.sendRunError(this.activeRun.eventId,this.activeRun.sessionId,s)}this.finishRun("failed",t.message);break}case"chat_segment":case"inference_start":case"iteration_start":case"chat_accepted":break;default:{n.debug("openhuman-adapter",`Unhandled event: ${e}`);break}}}startRun(t,e,s){this.activeRun={eventId:t,sessionId:e,requestId:"",threadId:s,chunkSeq:0,clientMsgId:`oh_${++this.clientMsgSeq}_${Date.now()}`,textBuffer:"",flushTimer:null}}finishRun(t,e){const s=this.activeRun;if(!s)return;if(this.completedEvents.add(s.eventId),this.completedEvents.size>1e3){const o=this.completedEvents.values();for(let h=0;h<500;h++)o.next();const c=[...this.completedEvents].slice(-500);this.completedEvents=new Set(c)}this.activeRun=null,this.emit("eventDone",s.eventId),this.stopComposing(),this.stopIdleTimer();const i=++s.chunkSeq,a=s.clientMsgId;t==="failed"&&e&&this.callbacks.sendRunError(s.eventId,s.sessionId,e),this.callbacks.sendFinalStreamChunkReliable?this.callbacks.sendFinalStreamChunkReliable(s.eventId,s.sessionId,i,a).then(()=>{this.callbacks.sendEventResult(s.eventId,t,e)}).catch(()=>{this.callbacks.sendStreamChunk(s.eventId,s.sessionId,"",i,!0,a),this.callbacks.sendEventResult(s.eventId,t,e)}):(this.callbacks.sendStreamChunk(s.eventId,s.sessionId,"",i,!0,a),this.callbacks.sendEventResult(s.eventId,t,e))}clearRun(){this.activeRun?.flushTimer&&clearTimeout(this.activeRun.flushTimer);const t=this.activeRun?.eventId;this.activeRun=null,t&&this.emit("eventDone",t)}appendText(t){if(this.activeRun){if(this.activeRun.textBuffer+=t,this.activeRun.textBuffer.length>=S){this.flushTextBuffer();return}this.scheduleTextFlush()}}scheduleTextFlush(){!this.activeRun||this.activeRun.flushTimer||(this.activeRun.flushTimer=setTimeout(()=>{this.activeRun&&(this.activeRun.flushTimer=null),this.flushTextBuffer()},x))}flushTextBuffer(){if(this.stopTextFlush(),!(!this.activeRun||!this.activeRun.textBuffer)){for(const t of k(this.activeRun.textBuffer))this.activeRun.chunkSeq++,this.callbacks.sendStreamChunk(this.activeRun.eventId,this.activeRun.sessionId,t,this.activeRun.chunkSeq,!1,this.activeRun.clientMsgId);this.activeRun.textBuffer=""}}stopTextFlush(){this.activeRun?.flushTimer&&(clearTimeout(this.activeRun.flushTimer),this.activeRun.flushTimer=null)}startComposing(t,e){this.stopComposing(),this.composingSessionId=t,this.callbacks.sendSessionComposing(t,!0),this.composingInterval=setInterval(()=>{this.composingSessionId&&this.callbacks.sendSessionComposing(this.composingSessionId,!0)},3e4)}stopComposing(){this.composingInterval&&(clearInterval(this.composingInterval),this.composingInterval=null),this.composingSessionId&&(this.callbacks.sendSessionComposing(this.composingSessionId,!1),this.composingSessionId=null)}resetIdleTimer(){this.stopIdleTimer(),!(this.stopped||!this.activeRun)&&(this.idleTimer=setTimeout(()=>{n.error("openhuman-adapter","Idle timeout \u2014 emitting exit for respawn"),this.flushTextBuffer(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed","idle timeout"),this.clearRun()),this.emit("exit",-1)},b))}stopIdleTimer(){this.idleTimer&&(clearTimeout(this.idleTimer),this.idleTimer=null)}resolveWorkspaceDir(){return this.options.workspaceDir?this.options.workspaceDir.replace(/^~/,d()):v(d(),".openhuman")}resolveCwd(){const t=(this.config.options??{}).cwd;return typeof t=="string"&&t?t:process.cwd()}buildPromptText(t){let e=t.text;return t.contextMessages&&t.contextMessages.length>0&&(e=t.contextMessages.map(i=>`[context] ${i.senderId}: ${i.content}`).join(`
|
|
2
2
|
`)+`
|
|
3
3
|
|
|
4
4
|
`+e),e}buildPromptTextFromEvent(t){let e=t.content||"";if(t.context_messages_json)try{const s=JSON.parse(t.context_messages_json);Array.isArray(s)&&s.length>0&&(e=s.map(a=>`[context] ${a.sender_id??"unknown"}: ${a.content}`).join(`
|
|
5
5
|
`)+`
|
|
6
6
|
|
|
7
|
-
`+e)}catch{}return e}}export{
|
|
7
|
+
`+e)}catch{}return e}}export{D as OpenHumanAdapter};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import{EventEmitter as p}from"node:events";import{resolveCommandPath as f,spawnCommand as v,killProcessGroup as u}from"../../core/runtime/spawn.js";import{PiTransport as g}from"./pi-transport.js";import{log as
|
|
2
|
-
${
|
|
3
|
-
`)}`,data:
|
|
4
|
-
`):"No skills found",data:i}}default:return{status:"unsupported",message:`Unknown command: ${t}`}}}catch(i){return{status:"failed",message:i instanceof Error?i.message:String(i)}}}resolveCwd(){if(this.bindingStore&&this.aibotSessionId){const e=this.bindingStore.get(this.aibotSessionId);if(e?.cwd)return e.cwd}const t=(this.config.options??{}).cwd;return typeof t=="string"&&t?t:process.cwd()}spawnPi(){const t=this.config.command||"pi",e=f(t,typeof process.env.PATH=="string"?process.env.PATH:void 0),s=this.config.args??[],n=s.some(o=>o.startsWith("--mode"))?s:["--mode","rpc",...s],
|
|
1
|
+
import{EventEmitter as p}from"node:events";import{resolveCommandPath as f,spawnCommand as v,killProcessGroup as u}from"../../core/runtime/spawn.js";import{PiTransport as g}from"./pi-transport.js";import{log as r}from"../../core/log/index.js";import{scanSkills as S}from"../claude/skill-scanner.js";import{SessionBindingStore as I}from"../../core/persistence/session-binding-store.js";import{splitTextForAibotProtocol as x}from"../../core/protocol/index.js";class T extends p{adapterSessionId;constructor(t){super(),this.adapterSessionId=t}emitDone(t){this.emit("done",t)}emitError(t){if(this.listenerCount("error")===0){r.warn("pi-adapter",`Prompt handle error (no listeners): ${t.message}`);return}this.emit("error",t)}async cancel(){}}const b=12e4,E=500,k=2e3;class m extends p{type="pi";config;callbacks;process=null;transport=new g;alive=!1;stopped=!1;piSessionPath=null;activeEventId=null;activeSessionId=null;isStreaming=!1;streamSeq=0;clientMsgSeq=0;activeClientMsgId=null;thinkingSeq=0;textBuffer="";emittedTextByIndex=new Map;textFlushTimer=null;idleTimer=null;composingSessionId=null;composingInterval=null;doneGuardTimer=null;bindingStore=null;aibotSessionId="";sessionReadyPromise=null;constructor(t,e){super(),this.config=t,this.callbacks=e;const s=t.options??{};this.aibotSessionId=String(s.aibotSessionId??"").trim(),this.bindingStore=s.bindingStore instanceof I?s.bindingStore:null,this.bindingStore&&this.aibotSessionId&&(this.piSessionPath=this.bindingStore.getPiSessionPath(this.aibotSessionId)??null)}async start(){this.spawnPi(),await this.ensureSessionReady(),r.info("pi-adapter",`Ready (pid=${this.process?.pid})`)}async stop(){if(this.stopped=!0,this.alive=!1,this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.cancelDoneGuard(),this.transport.close(),this.process){const t=this.process;try{u(t,"SIGTERM")}catch{}const e=setTimeout(()=>{try{u(t,"SIGKILL")}catch{}},5e3);t.on("exit",()=>clearTimeout(e)),this.process=null}}isAlive(){return this.alive}async createSession(t){this.sessionReadyPromise=null,await this.createNewSession();const e=this.piSessionPath||`pi-${Date.now()}`;return r.info("pi-adapter",`Session created: ${e} (path=${this.piSessionPath})`),e}async resumeSession(t,e){await this.switchSession()}async destroySession(t){this.piSessionPath=null,this.sessionReadyPromise=null,this.persistPiSessionPath(void 0)}sendPrompt(t){const e=new T(t.adapterSessionId),s=this.buildPromptTextFromRequest(t);return this.ensureSessionReady().then(()=>this.transport.send("prompt",{message:s})).then(i=>{i.success||e.emitDone({status:"failed",error:i.error})}).catch(i=>{e.emitError(i instanceof Error?i:new Error(String(i)))}),e}async cancel(t){try{await this.transport.send("abort")}catch{}}deliverInboundEvent(t){const{event_id:e,session_id:s,content:i}=t,n=this.buildPromptText(t);this.isStreaming?(this.activeEventId&&this.activeEventId!==e&&(r.info("pi-adapter",`steer: cancel ${this.activeEventId} -> ${e}`),this.flushTextBuffer(),this.callbacks.sendEventResult(this.activeEventId,"canceled","steered to new event")),this.activeEventId=e,this.activeSessionId=s,this.resetRunStreamState(),this.startComposing(s,e),this.resetIdleTimer(),this.transport.send("prompt",{message:n,streamingBehavior:"steer"}).catch(a=>{r.error("pi-adapter",`steer failed: ${a}`),this.callbacks.sendEventResult(e,"failed",String(a))})):(r.info("pi-adapter",`prompt: event=${e} session=${s}`),this.activeEventId=e,this.activeSessionId=s,this.isStreaming=!0,this.resetRunStreamState(),this.startComposing(s,e),this.resetIdleTimer(),this.transport.send("prompt",{message:n}).then(a=>{a.success||(r.error("pi-adapter",`prompt rejected: ${a.error}`),this.isStreaming=!1,this.stopComposing(),this.stopIdleTimer(),this.callbacks.sendEventResult(e,"failed",a.error),this.clearActive())}).catch(a=>{r.error("pi-adapter",`prompt error: ${a}`),this.isStreaming=!1,this.stopComposing(),this.stopIdleTimer(),this.callbacks.sendEventResult(e,"failed",String(a)),this.clearActive()}))}deliverStopEvent(t,e){if(this.activeEventId===t){r.info("pi-adapter",`stop: event=${t}, releasing busy immediately`),this.transport.send("abort").catch(()=>{}),this.flushTextBuffer();const s=this.nextStreamSeq(),i=this.activeClientMsgId??void 0;this.callbacks.sendStreamChunk(t,this.activeSessionId??"","",s,!0,i),this.isStreaming=!1,this.stopComposing(),this.stopIdleTimer(),this.callbacks.sendEventResult(t,"canceled","stopped by user"),this.clearActive()}}async handleLocalAction(t){const e=t.action_id;switch(t.action_type){case"set_model":{const{provider:s,modelId:i}=t.params??{};if(s&&i)try{return await this.transport.send("set_model",{provider:s,modelId:i}),this.callbacks.sendLocalActionResult(e,"ok",{outcome:"model_set",provider:s,modelId:i}),{handled:!0,kind:"set_model"}}catch(n){return this.callbacks.sendLocalActionResult(e,"failed",void 0,"set_model_error",n instanceof Error?n.message:String(n)),{handled:!0,kind:"set_model_error"}}return{handled:!1,kind:""}}case"get_context":try{const s=await this.transport.send("get_state");return this.callbacks.sendLocalActionResult(e,"ok",{state:s}),{handled:!0,kind:"get_context"}}catch(s){return this.callbacks.sendLocalActionResult(e,"failed",void 0,"get_context_error",s instanceof Error?s.message:String(s)),{handled:!1,kind:""}}case"pi_extension_ui_response":return this.transport.sendNoWait({type:"extension_ui_response",...t.params??{}}),this.callbacks.sendLocalActionResult(e,"ok"),{handled:!0,kind:"extension_ui_response"};default:return{handled:!1,kind:""}}}setPermissionHandler(t){}async ping(t){try{return await this.transport.send("get_state"),!0}catch{return!1}}getStatus(){return{alive:this.alive,busy:this.isStreaming,sessions:this.piSessionPath?1:0}}getActiveEventIds(){return this.activeEventId?[this.activeEventId]:[]}clearActiveEventForShutdown(){this.stopIdleTimer(),this.activeEventId=null}getMcpConfig(){return null}getSupportedCommands(){return[{name:"model",description:"List or set model",args:"[provider:model_id]"},{name:"interrupt",description:"Interrupt current run"},{name:"status",description:"Show session status"},{name:"skills",description:"List available skills"}]}async execCommand(t,e,s){try{if(!this.alive)return{status:"failed",message:"Pi process not running"};switch(t){case"model":{const i=e.trim();if(i){const c=i.indexOf(":");if(c<1)return{status:"failed",message:"Format: provider:model_id"};const o=i.slice(0,c),d=i.slice(c+1);if(!d)return{status:"failed",message:"Format: provider:model_id"};const l=await this.transport.send("set_model",{provider:o,modelId:d});return l?.success?{status:"ok",message:`Model set to ${o}:${d}`}:{status:"failed",message:`Failed to set model: ${l?.error??"unknown error"}`}}const a=(await this.transport.send("get_available_models"))?.data?.models;return a&&a.length>0?{status:"ok",message:`Available models:
|
|
2
|
+
${a.map(o=>` ${o.provider??"unknown"}:${o.id} (${o.name??o.id})`).join(`
|
|
3
|
+
`)}`,data:a}:{status:"ok",message:"No models available",data:[]}}case"interrupt":return this.isStreaming?(await this.transport.send("abort"),this.activeEventId&&this.callbacks.sendEventResult(this.activeEventId,"canceled","interrupted"),this.clearActive(),{status:"ok",message:"Run interrupted"}):{status:"failed",message:"No active run to interrupt"};case"status":{const i=this.getStatus();return{status:"ok",message:`Alive: ${i.alive}, Busy: ${i.busy}, Session: ${this.piSessionPath??"none"}`,data:{alive:i.alive,busy:i.busy,sessions:i.sessions,sessionPath:this.piSessionPath}}}case"skills":{const i=S({mode:"pi"}),n=i.map(a=>`- ${a.name}${a.trigger?` (${a.trigger})`:""} [${a.source}]: ${a.description}`);return{status:"ok",message:n.length>0?n.join(`
|
|
4
|
+
`):"No skills found",data:i}}default:return{status:"unsupported",message:`Unknown command: ${t}`}}}catch(i){return{status:"failed",message:i instanceof Error?i.message:String(i)}}}resolveCwd(){if(this.bindingStore&&this.aibotSessionId){const e=this.bindingStore.get(this.aibotSessionId);if(e?.cwd)return e.cwd}const t=(this.config.options??{}).cwd;return typeof t=="string"&&t?t:process.cwd()}spawnPi(){const t=this.config.command||"pi",e=f(t,typeof process.env.PATH=="string"?process.env.PATH:void 0),s=this.config.args??[],n=s.some(o=>o.startsWith("--mode"))?s:["--mode","rpc",...s],a={...process.env,...this.config.env},c=this.resolveCwd();r.info("pi-adapter",`Spawning: ${e} ${n.join(" ")}`);try{this.process=v(e,n,{env:a,cwd:c}).process}catch(o){throw r.error("pi-adapter",`PI spawn threw: ${o}`),this.alive=!1,o}this.process.on("error",o=>{r.error("pi-adapter",`Spawn error: ${o.message}`),this.alive=!1,this.transport.close(),this.activeEventId&&(this.callbacks.sendEventResult(this.activeEventId,"failed",`Spawn error: ${o.message}`),this.clearActive()),this.stopped||this.emit("exit",1)}),this.process.on("exit",o=>{r.info("pi-adapter",`PI process exited (code=${o})`),this.alive=!1,this.transport.close(),this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.activeEventId&&this.callbacks.sendEventResult(this.activeEventId,"failed",`PI process exited (code=${o})`),this.isStreaming=!1,this.clearActive(),this.stopped||this.emit("exit",o??1)}),this.process.stderr?.on("data",o=>{const d=o.toString().trim();d&&r.info("pi-adapter",`[pi stderr] ${d}`)}),this.transport.on("event",o=>this.handlePiEvent(o)),this.transport.bind(this.process.stdin,this.process.stdout),this.alive=!0}handlePiEvent(t){if(this.stopped)return;switch(this.resetIdleTimer(),t.type){case"message_update":{const s=t.assistantMessageEvent;if(!s)break;const i=s.type;if(i==="text_delta"){const n=s.delta;if(n){const a=typeof s.contentIndex=="number"?s.contentIndex:0;this.rememberEmittedText(a,n),this.appendText(n)}}else if(i==="text_end"){const n=s.content,a=typeof s.contentIndex=="number"?s.contentIndex:0;n&&this.appendMissingText(a,n)}else if(i==="thinking_delta"){const n=s.delta;n&&this.activeEventId&&this.activeSessionId&&(this.thinkingSeq++,this.callbacks.sendThinking(this.activeEventId,this.activeSessionId,n))}else if(i==="done"||i==="error"){if(this.flushTextBuffer(),i==="error"&&this.activeEventId&&this.activeSessionId){const n=s.reason??"stream error";this.callbacks.sendRunError(this.activeEventId,this.activeSessionId,String(n),this.nextStreamSeq(),this.activeClientMsgId??void 0)}this.scheduleDoneGuard()}break}case"tool_execution_start":{if(this.cancelDoneGuard(),this.flushTextBuffer(),!this.activeEventId||!this.activeSessionId)break;const s=t;if(s.toolName){const i=typeof s.args=="object"&&s.args!==null?JSON.stringify(s.args):String(s.args??"");this.callbacks.sendToolUse(this.activeEventId,this.activeSessionId,s.toolName,i)}break}case"tool_execution_end":{if(!this.activeEventId||!this.activeSessionId)break;const s=t,i=_(s.result);i&&this.callbacks.sendToolResult(this.activeEventId,this.activeSessionId,s.toolName,i);break}case"agent_end":{if(r.info("pi-adapter",`agent_end event=${this.activeEventId} sessionId=${this.activeSessionId}`),this.cancelDoneGuard(),this.flushTextBuffer(),this.isStreaming=!1,this.stopComposing(),this.stopIdleTimer(),this.activeEventId){const s=this.activeEventId,i=this.activeSessionId??"",n=this.activeClientMsgId??void 0;this.clearActive(),this.finalizeEvent(s,i,n)}else this.clearActive();break}case"agent_start":{this.cancelDoneGuard(),this.activeEventId&&(r.info("pi-adapter",`agent_start event=${this.activeEventId}`),this.isStreaming=!0);break}default:break}}appendText(t){if(this.textBuffer+=t,this.textBuffer.length>=k){this.flushTextBuffer();return}this.scheduleTextFlush()}scheduleTextFlush(){this.textFlushTimer||(this.textFlushTimer=setTimeout(()=>{this.textFlushTimer=null,this.flushTextBuffer()},E))}flushTextBuffer(){this.stopTextFlush(),!(!this.textBuffer||!this.activeEventId||!this.activeSessionId)&&(this.sendTextChunks(this.textBuffer),this.textBuffer="")}sendTextChunks(t){if(!(!this.activeEventId||!this.activeSessionId))for(const e of x(t))this.callbacks.sendStreamChunk(this.activeEventId,this.activeSessionId,e,this.nextStreamSeq(),!1,this.activeClientMsgId??void 0)}stopTextFlush(){this.textFlushTimer&&(clearTimeout(this.textFlushTimer),this.textFlushTimer=null)}buildPromptText(t){let e=t.content||"";if(t.context_messages_json)try{const s=JSON.parse(t.context_messages_json);Array.isArray(s)&&s.length>0&&(e=s.map(n=>`[context] ${n.sender_id??"unknown"}: ${n.content}`).join(`
|
|
5
5
|
`)+`
|
|
6
6
|
|
|
7
7
|
`+e)}catch{}return e}buildPromptTextFromRequest(t){let e=t.text;return t.contextMessages&&t.contextMessages.length>0&&(e=t.contextMessages.map(i=>`[context] ${i.senderId}: ${i.content}`).join(`
|
|
8
8
|
`)+`
|
|
9
9
|
|
|
10
|
-
`+e),e}clearActive(){this.activeEventId=null,this.activeSessionId=null,this.activeClientMsgId=null,this.textBuffer="",this.emittedTextByIndex.clear()}resetRunStreamState(){this.streamSeq=0,this.thinkingSeq=0,this.textBuffer="",this.emittedTextByIndex.clear(),this.activeClientMsgId=`pi_${++this.clientMsgSeq}_${Date.now()}`}nextStreamSeq(){return this.streamSeq++,this.streamSeq}sendFinalStreamChunk(t,e){this.callbacks.sendStreamChunk(t,e,"",this.nextStreamSeq(),!0,this.activeClientMsgId??void 0)}finalizeEvent(t,e,s){const i=this.nextStreamSeq();this.callbacks.sendFinalStreamChunkReliable?this.callbacks.sendFinalStreamChunkReliable(t,e,i,s).then(()=>{this.callbacks.sendEventResult(t,"responded"),
|
|
10
|
+
`+e),e}clearActive(){const t=this.activeEventId;this.activeEventId=null,this.activeSessionId=null,this.activeClientMsgId=null,this.textBuffer="",this.emittedTextByIndex.clear(),t&&this.emit("eventDone",t)}resetRunStreamState(){this.streamSeq=0,this.thinkingSeq=0,this.textBuffer="",this.emittedTextByIndex.clear(),this.activeClientMsgId=`pi_${++this.clientMsgSeq}_${Date.now()}`}nextStreamSeq(){return this.streamSeq++,this.streamSeq}sendFinalStreamChunk(t,e){this.callbacks.sendStreamChunk(t,e,"",this.nextStreamSeq(),!0,this.activeClientMsgId??void 0)}finalizeEvent(t,e,s){const i=this.nextStreamSeq();this.callbacks.sendFinalStreamChunkReliable?this.callbacks.sendFinalStreamChunkReliable(t,e,i,s).then(()=>{this.callbacks.sendEventResult(t,"responded"),r.info("pi-adapter",`event completed (reliable) event=${t}`)}).catch(n=>{r.error("pi-adapter",`finalStreamChunk ACK failed event=${t}: ${n}`),this.callbacks.sendStreamChunk(t,e,"",i,!0,s),this.callbacks.sendEventResult(t,"responded"),r.info("pi-adapter",`event completed (fallback) event=${t}`)}):(this.callbacks.sendStreamChunk(t,e,"",i,!0,s),this.callbacks.sendEventResult(t,"responded"),r.info("pi-adapter",`event completed event=${t}`))}rememberEmittedText(t,e){this.emittedTextByIndex.set(t,(this.emittedTextByIndex.get(t)??"")+e)}appendMissingText(t,e){const s=this.emittedTextByIndex.get(t)??"";if(e!==s){if(e.startsWith(s)){const i=e.slice(s.length);i&&(this.rememberEmittedText(t,i),this.appendText(i));return}if(!s){this.rememberEmittedText(t,e),this.appendText(e);return}r.info("pi-adapter",`text_end content mismatch at index=${t}, keeping streamed deltas`)}}startComposing(t,e){this.stopComposing(),this.composingSessionId=t,this.callbacks.sendSessionActivitySet(t,"composing",!0,{ref_event_id:e,ttl_ms:12e4}),this.composingInterval=setInterval(()=>{this.composingSessionId&&this.callbacks.sendSessionActivitySet(this.composingSessionId,"composing",!0,{ttl_ms:12e4})},3e4)}stopComposing(){this.composingInterval&&(clearInterval(this.composingInterval),this.composingInterval=null),this.composingSessionId&&(this.callbacks.sendSessionActivitySet(this.composingSessionId,"composing",!1),this.composingSessionId=null)}static DONE_GUARD_MS=5e3;scheduleDoneGuard(){this.cancelDoneGuard(),!(!this.activeEventId||!this.activeSessionId)&&(this.doneGuardTimer=setTimeout(()=>{if(this.doneGuardTimer=null,!this.activeEventId||!this.isStreaming)return;r.info("pi-adapter",`done guard triggered \u2014 no agent_end received, ending event=${this.activeEventId}`),this.flushTextBuffer(),this.isStreaming=!1,this.stopComposing(),this.stopIdleTimer();const t=this.activeEventId,e=this.activeSessionId??"",s=this.activeClientMsgId??void 0;this.clearActive(),this.finalizeEvent(t,e,s)},m.DONE_GUARD_MS))}cancelDoneGuard(){this.doneGuardTimer&&(clearTimeout(this.doneGuardTimer),this.doneGuardTimer=null)}resetIdleTimer(){this.stopIdleTimer(),!(this.stopped||!this.isStreaming)&&(this.idleTimer=setTimeout(()=>{r.error("pi-adapter","Idle timeout \u2014 emitting exit for respawn"),this.flushTextBuffer(),this.isStreaming=!1,this.stopComposing(),this.activeEventId&&this.callbacks.sendEventResult(this.activeEventId,"failed","idle timeout"),this.clearActive(),this.emit("exit",-1)},b))}stopIdleTimer(){this.idleTimer&&(clearTimeout(this.idleTimer),this.idleTimer=null)}async ensureSessionReady(){return this.sessionReadyPromise?this.sessionReadyPromise:(this.sessionReadyPromise=this.restoreOrCreateSession().finally(()=>{this.sessionReadyPromise=null}),this.sessionReadyPromise)}async restoreOrCreateSession(){try{await this.switchSession();return}catch(t){this.piSessionPath&&r.error("pi-adapter",`switch_session failed, creating new session: ${t}`)}await this.createNewSession()}async switchSession(){if(!this.piSessionPath)throw new Error("no PI session path");const t=await this.transport.send("switch_session",{sessionPath:this.piSessionPath});if(!t.success)throw this.piSessionPath=null,this.persistPiSessionPath(void 0),new Error(`switch_session failed: ${t.error}`)}async createNewSession(){const t=await this.transport.send("new_session");if(!t.success)throw new Error(`new_session failed: ${t.error}`);let e=null;if(e=t.data?.sessionFile??null,!e)try{const i=await this.transport.send("get_state");i.success&&(e=i.data?.sessionFile??null)}catch{}this.piSessionPath=e,this.persistPiSessionPath(this.piSessionPath??void 0)}persistPiSessionPath(t){!this.bindingStore||!this.aibotSessionId||this.bindingStore.setPiSessionPath(this.aibotSessionId,t)}}function _(h){if(!h||typeof h!="object")return"";const t=h.content;if(!Array.isArray(t))return"";const e=[];for(const s of t)if(s&&typeof s=="object"){const i=s;i.type==="text"&&typeof i.text=="string"&&e.push(i.text)}return e.join("")}export{m as PiAdapter};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{mkdir as m}from"node:fs/promises";import{join as c,basename as l}from"node:path";import{homedir as
|
|
1
|
+
import{mkdir as m}from"node:fs/promises";import{join as c,basename as l}from"node:path";import{homedir as _}from"node:os";import{listFiles as u}from"./list-files.js";function d(){return _()}async function f(a,s){const o=a.params??{},t=o.parent_id?.trim(),i=o.show_hidden??!1;let e;t?e=t:e=s.resolveCwd()||d();try{return{status:"ok",result:{files:await u(e,i),current_path:e}}}catch(r){return r?.code==="ENOENT"?{status:"failed",error_code:"path_not_found",error_msg:`Directory not found: ${e}`}:r?.code==="ENOTDIR"?{status:"failed",error_code:"not_a_directory",error_msg:`Not a directory: ${e}`}:{status:"failed",error_code:"list_failed",error_msg:String(r.message||r)}}}async function p(a,s){const o=a.params??{},t=o.name?.trim(),i=o.parent_id?.trim();if(!t)return{status:"failed",error_code:"name_required",error_msg:"Folder name is required"};if(/[/\\]/.test(t))return{status:"failed",error_code:"invalid_name",error_msg:"Folder name must not contain path separators"};const e=i||s.resolveCwd()||d(),r=c(e,t);try{return await m(r),{status:"ok",result:{id:r,name:l(r),is_directory:!0}}}catch(n){return n?.code==="EEXIST"?{status:"failed",error_code:"already_exists",error_msg:`Folder already exists: ${r}`}:{status:"failed",error_code:"create_failed",error_msg:String(n.message||n)}}}export{p as handleCreateFolderAction,f as handleFileListAction};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{readdir as r,stat as m}from"node:fs/promises";import{join as l,extname as d}from"node:path";const x={pdf:"application/pdf",doc:"application/msword",docx:"application/vnd.openxmlformats-officedocument.wordprocessingml.document",xls:"application/vnd.ms-excel",xlsx:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",ppt:"application/vnd.ms-powerpoint",pptx:"application/vnd.openxmlformats-officedocument.presentationml.presentation",txt:"text/plain",md:"text/markdown",csv:"text/csv",json:"application/json",xml:"application/xml",yaml:"text/yaml",yml:"text/yaml",html:"text/html",css:"text/css",js:"text/javascript",ts:"text/typescript",zip:"application/zip",rar:"application/x-rar-compressed","7z":"application/x-7z-compressed",tar:"application/x-tar",gz:"application/gzip",jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",gif:"image/gif",webp:"image/webp",svg:"image/svg+xml",mp4:"video/mp4",mov:"video/quicktime",avi:"video/x-msvideo",mkv:"video/x-matroska",webm:"video/webm",mp3:"audio/mpeg",wav:"audio/wav",flac:"audio/flac",aac:"audio/aac"};function n(a){const p=d(a).slice(1).toLowerCase();return x[p]}async function f(a,p=!1){const c=await r(a,{withFileTypes:!0}),s=[];for(const
|
|
1
|
+
import{readdir as r,stat as m}from"node:fs/promises";import{join as l,extname as d}from"node:path";const x={pdf:"application/pdf",doc:"application/msword",docx:"application/vnd.openxmlformats-officedocument.wordprocessingml.document",xls:"application/vnd.ms-excel",xlsx:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",ppt:"application/vnd.ms-powerpoint",pptx:"application/vnd.openxmlformats-officedocument.presentationml.presentation",txt:"text/plain",md:"text/markdown",csv:"text/csv",json:"application/json",xml:"application/xml",yaml:"text/yaml",yml:"text/yaml",html:"text/html",css:"text/css",js:"text/javascript",ts:"text/typescript",zip:"application/zip",rar:"application/x-rar-compressed","7z":"application/x-7z-compressed",tar:"application/x-tar",gz:"application/gzip",jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",gif:"image/gif",webp:"image/webp",svg:"image/svg+xml",mp4:"video/mp4",mov:"video/quicktime",avi:"video/x-msvideo",mkv:"video/x-matroska",webm:"video/webm",mp3:"audio/mpeg",wav:"audio/wav",flac:"audio/flac",aac:"audio/aac"};function n(a){const p=d(a).slice(1).toLowerCase();return x[p]}async function f(a,p=!1){const c=await r(a,{withFileTypes:!0}),s=[];for(const t of c){if(!p&&t.name.startsWith("."))continue;const i=l(a,t.name),e={id:i,name:t.name,is_directory:t.isDirectory()};try{if(t.isDirectory()){const o=await m(i);e.modified_at=o.mtime.toISOString()}else{const o=await m(i);e.size=o.size,e.modified_at=o.mtime.toISOString(),e.mime_type=n(t.name)}}catch{}s.push(e)}return s.sort((t,i)=>t.is_directory!==i.is_directory?t.is_directory?-1:1:t.name.localeCompare(i.name)),s}export{f as listFiles,n as resolveMimeType};
|
package/dist/log.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{createWriteStream as g,mkdirSync as l,existsSync as f}from"node:fs";import{join as
|
|
2
|
-
`)},error(o,n
|
|
1
|
+
import{createWriteStream as g,mkdirSync as l,existsSync as f}from"node:fs";import{join as e}from"node:path";import{homedir as m}from"node:os";const i=e(m(),".grix"),s={base:i,config:e(i,"config"),log:e(i,"log"),data:e(i,"data")};function S(){for(const o of Object.values(s))f(o)||l(o,{recursive:!0})}let a=null;function $(){const o=new Date().toISOString().slice(0,10),t=e(s.log,`grix-acp-${o}.log`);a=g(t,{flags:"a"})}function c(){return new Date().toISOString().slice(11,19)}const u={info(o,t,...n){const r=`${c()} [${o}] ${t}${n.length?" "+n.map(String).join(" "):""}`;console.log(r),a?.write(r+`
|
|
2
|
+
`)},error(o,t,...n){const r=`${c()} [${o}] ERROR ${t}${n.length?" "+n.map(String).join(" "):""}`;console.error(r),a?.write(r+`
|
|
3
3
|
`)}};export{s as GRIX_PATHS,S as ensureGrixDirs,$ as initLogger,u as log};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import*as
|
|
1
|
+
import*as i from"node:net";const e={bind:"127.0.0.1",port:0,endpoint:"/mcp",sessionTimeoutMs:18e5,invokeTimeoutMs:3e4};function n(o){const u={bind:o?.bind??e.bind,port:o?.port??e.port,endpoint:o?.endpoint??e.endpoint,sessionTimeoutMs:o?.sessionTimeoutMs??e.sessionTimeoutMs,invokeTimeoutMs:o?.invokeTimeoutMs??e.invokeTimeoutMs,allowedOrigins:o?.allowedOrigins,allowedHosts:o?.allowedHosts};return s(u.bind),u.port!==0&&t(u.port),r(u.sessionTimeoutMs),u}function s(o){if(!o||!i.isIPv4(o)&&!i.isIPv6(o))throw new Error(`\u914D\u7F6E\u6821\u9A8C\u5931\u8D25: bind \u5730\u5740 "${o}" \u4E0D\u662F\u5408\u6CD5\u7684 IPv4 \u6216 IPv6 \u5730\u5740`)}function t(o){if(!Number.isInteger(o)||o<1||o>65535)throw new Error(`\u914D\u7F6E\u6821\u9A8C\u5931\u8D25: port \u503C ${o} \u4E0D\u5728\u5408\u6CD5\u8303\u56F4 1-65535 \u5185\u6216\u4E0D\u662F\u6574\u6570`)}function r(o){if(!Number.isInteger(o)||o<1e3||o>864e5)throw new Error(`\u914D\u7F6E\u6821\u9A8C\u5931\u8D25: session_timeout_ms \u503C ${o} \u4E0D\u5728\u5408\u6CD5\u8303\u56F4 1000-86400000 \u5185`)}export{n as createDefaultGatewayConfig};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const
|
|
1
|
+
const r=3e4;class c{connectionManager;onDisconnected;bindings=new Map;constructor(n,i){this.connectionManager=n,this.onDisconnected=i}async bind(n,i){if(this.bindings.has(n))throw new Error(`Session ${n} is already bound to a connection`);const e=await this.connectWithTimeout(i),t=[],s=e.onDisconnected(()=>{this.removeBinding(n),this.onDisconnected(n)});return t.push(s),this.bindings.set(n,{sessionId:n,handle:e,subscriptions:t}),e}getHandle(n){return this.bindings.get(n)?.handle}unbind(n){const i=this.bindings.get(n);if(i){this.bindings.delete(n);for(const e of i.subscriptions)e();i.handle.disconnect()}}unbindAll(){const n=[...this.bindings.keys()];for(const i of n)this.unbind(i)}connectWithTimeout(n){return new Promise((i,e)=>{let t=!1;const s=setTimeout(()=>{t||(t=!0,e(new Error("Connection bind timeout after 30000ms")))},3e4);this.connectionManager.connect({agentId:n.agentId,apiKey:n.apiKey,url:n.wsUrl,clientType:n.clientType,capabilities:["agent_invoke"],adapterHint:`${n.clientType}/base`},{maxRetries:0}).then(o=>{t?o.disconnect():(t=!0,clearTimeout(s),i(o))}).catch(o=>{t||(t=!0,clearTimeout(s),e(o))})})}removeBinding(n){const i=this.bindings.get(n);if(i){this.bindings.delete(n);for(const e of i.subscriptions)e()}}}export{c as ConnectionBindingImpl};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createServer as N}from"node:http";import{Server as O}from"@modelcontextprotocol/sdk/server/index.js";import{StreamableHTTPServerTransport as j}from"@modelcontextprotocol/sdk/server/streamableHttp.js";import{ListToolsRequestSchema as R,CallToolRequestSchema as E}from"@modelcontextprotocol/sdk/types.js";import{createSecurityPolicy as h}from"./security.js";import{SessionManagerImpl as
|
|
1
|
+
import{createServer as N}from"node:http";import{Server as O}from"@modelcontextprotocol/sdk/server/index.js";import{StreamableHTTPServerTransport as j}from"@modelcontextprotocol/sdk/server/streamableHttp.js";import{ListToolsRequestSchema as R,CallToolRequestSchema as E}from"@modelcontextprotocol/sdk/types.js";import{createSecurityPolicy as h}from"./security.js";import{SessionManagerImpl as q}from"./session-manager.js";import{ToolRegistryImpl as A}from"./tool-registry.js";import{ToolExecutorImpl as C}from"./tool-executor.js";function J(n,r){let a=null,p=!1,m,c;const S=new A,v=new C;let s=null;return{async start(){m=h({serverPort:n.port,allowedOrigins:n.allowedOrigins??[],allowedHosts:n.allowedHosts??[]}),c=new q({maxSessions:1,sessionTimeoutMs:n.sessionTimeoutMs,onSessionExpired:e=>{w(e)}}),a=N((e,t)=>{g(e,t)}),await new Promise((e,t)=>{a.on("error",t),a.listen(n.port,n.bind,()=>{a.removeListener("error",t),p=!0;const i=a.address();i&&typeof i=="object"&&(n.port=i.port,m=h({serverPort:n.port,allowedOrigins:n.allowedOrigins??[],allowedHosts:n.allowedHosts??[]})),e()})})},async stop(){if(p=!1,a&&(await new Promise(e=>{a.close(()=>e())}),a=null),s){try{await s.transport.close()}catch{}try{await s.mcpServer.close()}catch{}s=null}c&&(await c.closeAll(),c.dispose())},getStatus(){return{listening:p,url:`http://${n.bind}:${n.port}${n.endpoint}`,activeSessions:s?1:0}},pushEvent(e){s&&y(s.mcpServer,"notifications/message",{event_id:e.event_id,session_id:e.session_id,sender_id:e.sender_id??"",content:e.content,msg_type:e.msg_type??1,msg_id:e.msg_id??"",session_type:e.session_type??1,quoted_message_id:e.quoted_message_id??"",attachments:e.attachments??[],context_messages:e.context_messages??[]},s.transport)},pushStop(e){s&&y(s.mcpServer,"notifications/event_stop",{event_id:e.event_id,stop_id:e.stop_id,session_id:e.session_id,reason:e.reason??""},s.transport)},pushRevoke(e){s&&y(s.mcpServer,"notifications/event_revoke",{event_id:e.event_id,session_id:e.session_id,reason:e.reason??""},s.transport)},pushLocalAction(e){s&&y(s.mcpServer,"notifications/local_action",e,s.transport)}};async function g(e,t){if(new URL(e.url??"/",`http://${e.headers.host??"localhost"}`).pathname!==n.endpoint){t.writeHead(404,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:"Not Found"}));return}const i=m.validateRequest(e);if(!i.ok){t.writeHead(i.statusCode??403,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:i.message}));return}const o=e.method?.toUpperCase();if(o==="GET"){const d=f(e);if(!d||!s||s.sessionId!==d){t.writeHead(400,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:"Bad Request: missing or invalid session"}));return}c.touchActivity(d),await s.transport.handleRequest(e,t);return}if(o==="DELETE"){await x(e,t);return}if(o==="POST"){await _(e,t);return}t.writeHead(405,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:"Method Not Allowed"}))}async function _(e,t){const i=await b(e);let o;try{o=JSON.parse(i)}catch{t.writeHead(400,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:"Invalid JSON body"}));return}const d=I(o),l=e.headers.accept??"";if(!l.includes("application/json")||!l.includes("text/event-stream")){t.writeHead(406,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:"Not Acceptable: Accept header must include both application/json and text/event-stream"}));return}if(d)await T(e,t,o);else{const u=f(e);if(!u){t.writeHead(400,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:"Bad Request: missing Mcp-Session-Id header"}));return}if(!s||s.sessionId!==u){t.writeHead(404,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:"Not Found: session does not exist or has expired"}));return}c.touchActivity(u),await s.transport.handleRequest(e,t,o)}}async function T(e,t,i){if(s){t.writeHead(503,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:"Service Unavailable: gateway already has an active session"}));return}const o=c.createSession().sessionId,d=new j({sessionIdGenerator:()=>o,enableJsonResponse:!1,onsessioninitialized:()=>{},onsessionclosed:()=>{w(o)}}),l=new O({name:"grix-mcp-server",version:"1.0.0"},{capabilities:{tools:{}}});H(l,o),await l.connect(d),s={transport:d,mcpServer:l,sessionId:o},await d.handleRequest(e,t,i);try{c.markReady(o)}catch{}}async function x(e,t){const i=f(e);if(!i){t.writeHead(400,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:"Bad Request: missing Mcp-Session-Id header"}));return}if(!s||s.sessionId!==i){t.writeHead(404,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:"Not Found: session does not exist or has expired"}));return}await s.transport.handleRequest(e,t)}function H(e,t){const i=S.getTools();e.setRequestHandler(R,async()=>({tools:i.map(o=>({name:o.name,description:o.description,inputSchema:o.inputSchema}))})),e.setRequestHandler(E,async o=>{const{name:d,arguments:l}=o.params,u=c.getSession(t);return!u||u.state!=="ready"?{content:[{type:"text",text:`Session \u72B6\u6001\u4E0D\u53EF\u7528: ${u?.state??"unknown"}`}],isError:!0}:r.status!=="ready"?{content:[{type:"text",text:`\u8FDE\u63A5\u4E0D\u53EF\u7528: \u5F53\u524D\u72B6\u6001\u4E3A ${r.status}`}],isError:!0}:(c.touchActivity(t),v.execute(r,d,l??{},n.invokeTimeoutMs))})}async function w(e){if(s?.sessionId===e){try{await s.mcpServer.close()}catch{}s=null}try{await c.closeSession(e)}catch{}}}function y(n,r,a,p){return p?p.send({jsonrpc:"2.0",method:r,params:a}):Promise.resolve()}function f(n){const r=n.headers["mcp-session-id"];return Array.isArray(r)?r[0]:r||void 0}function I(n){return n&&typeof n=="object"&&"method"in n?n.method==="initialize":Array.isArray(n)?n.some(r=>r&&typeof r=="object"&&r.method==="initialize"):!1}function b(n){return new Promise((r,a)=>{const p=[];n.on("data",m=>p.push(m)),n.on("end",()=>r(Buffer.concat(p).toString("utf-8"))),n.on("error",a)})}export{J as createMcpGateway};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
function a(
|
|
1
|
+
function a(o){const e=new Set([`http://127.0.0.1:${o.serverPort}`,`http://localhost:${o.serverPort}`,...o.allowedOrigins]),t=new Set([`127.0.0.1:${o.serverPort}`,`localhost:${o.serverPort}`,...o.allowedHosts]);return{validateRequest(s){const r=i(s,e);if(!r.ok)return r;const n=l(s,t);return n.ok?{ok:!0}:n}}}function i(o,e){const t=o.headers.origin;return t?e.has(t)?{ok:!0}:{ok:!1,statusCode:403,message:`Origin not allowed: ${t}`}:{ok:!0}}function l(o,e){const t=o.headers.host;return t?e.has(t)?{ok:!0}:{ok:!1,statusCode:403,message:`Host not allowed: ${t}`}:{ok:!1,statusCode:403,message:"Missing Host header"}}export{a as createSecurityPolicy};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{toolCallToInvoke as i}from"../../core/mcp/tools.js";import{ToolRegistryImpl as l}from"./tool-registry.js";import{validateToolArgs as a}from"./tool-schemas.js";import{isEventTool as p,executeEventTool as d}from"./event-tool-executor.js";class y{registry;constructor(){this.registry=new l}async execute(
|
|
1
|
+
import{toolCallToInvoke as i}from"../../core/mcp/tools.js";import{ToolRegistryImpl as l}from"./tool-registry.js";import{validateToolArgs as a}from"./tool-schemas.js";import{isEventTool as p,executeEventTool as d}from"./event-tool-executor.js";class y{registry;constructor(){this.registry=new l}async execute(t,e,r,n){if(!this.registry.hasTool(e))return this.errorResult(`\u672A\u77E5\u5DE5\u5177: ${e}`);const s=a(e,r);if(!s.valid)return this.errorResult(`\u53C2\u6570\u6821\u9A8C\u5931\u8D25: ${s.error}`);if(t.status!=="ready")return this.errorResult(`\u8FDE\u63A5\u4E0D\u53EF\u7528: \u5F53\u524D\u72B6\u6001\u4E3A ${t.status}`);if(p(e))return this.executeEventTool(t,e,r);const o=i(e,r);try{const u=await t.agentInvoke(o.action,o.params,n);return this.normalizeResult(u)}catch(u){const c=u instanceof Error?u.message:String(u);return c.toLowerCase().includes("timeout")?this.errorResult(`\u8C03\u7528\u8D85\u65F6: ${c}`):this.errorResult(`\u8C03\u7528\u5931\u8D25: ${c}`)}}normalizeResult(t){if(t==null||typeof t!="object")return this.successResult(t??null);const e=t,r=typeof e.code=="number"?e.code:0;if(r===0){const s="data"in e?e.data:null;return this.successResult(s??null)}const n=typeof e.msg=="string"?e.msg:"\u672A\u77E5\u9519\u8BEF";return this.errorResult(`\u4E0A\u6E38\u9519\u8BEF [code=${r}]: ${n}`)}successResult(t){return{content:[{type:"text",text:JSON.stringify(t)}],isError:!1}}errorResult(t){return{content:[{type:"text",text:t}],isError:!0}}async executeEventTool(t,e,r){return e==="grix_access_control"?this.executeAccessControl(t,r):d(t,e,r)}async executeAccessControl(t,e){const r=String(e.action??""),n={pair_approve:"pair_approve",pair_deny:"pair_deny",allow_sender:"sender_allow",remove_sender:"sender_remove",set_policy:"policy_set"}[r];if(!n)return this.errorResult(`\u672A\u77E5 access_control action: ${r}`);const s={};e.code!=null&&(s.code=e.code),e.sender_id!=null&&(s.sender_id=e.sender_id),e.policy!=null&&(s.policy=e.policy);try{const o=await t.agentInvoke("claude_access_control",{verb:n,payload:s},3e4);return this.successResult(o)}catch(o){const u=o instanceof Error?o.message:String(o);return this.errorResult(`access_control \u8C03\u7528\u5931\u8D25: ${u}`)}}}export{y as ToolExecutorImpl};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{TOOLS as
|
|
1
|
+
import{TOOLS as s,EVENT_TOOLS as t}from"../../core/mcp/tools.js";const e=new Set(["grix_query","grix_group","grix_message_send","grix_message_unsend","grix_admin"]),r=new Set(["grix_reply","grix_complete","grix_event_ack","grix_composing","grix_access_control","grix_status"]);class a{tools;toolMap;constructor(){this.tools=[...s.filter(o=>e.has(o.name)),...t.filter(o=>r.has(o.name))],this.toolMap=new Map(this.tools.map(o=>[o.name,o]))}getTools(){return this.tools}getTool(o){return this.toolMap.get(o)}hasTool(o){return this.toolMap.has(o)}}export{a as ToolRegistryImpl};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const o={required:["action"],properties:{action:{type:"string",enum:["contact_search","session_search","message_history","message_search"]},id:{type:"string"},keyword:{type:"string",maxLength:200},limit:{type:"integer",minimum:1,maximum:100},offset:{type:"integer",minimum:0},sessionId:{type:"string"},beforeId:{type:"string"}}},a={required:["action"],properties:{action:{type:"string",enum:["create","detail","leave","add_members","remove_members","update_member_role","update_all_members_muted","update_member_speaking","dissolve"]},sessionId:{type:"string"},name:{type:"string",maxLength:128},memberIds:{type:"array",items:{type:"string"},maxItems:100},memberTypes:{type:"array",items:{type:"integer",enum:[1,2]}},memberId:{type:"string"},role:{type:"integer",enum:[1,2]},memberType:{type:"integer"},allMembersMuted:{type:"boolean"},isSpeakMuted:{type:"boolean"},canSpeakWhenAllMuted:{type:"boolean"}}},p={required:["sessionId","content"],properties:{sessionId:{type:"string"},content:{type:"string",maxLength:1e4},msgType:{type:"integer"},quotedMessageId:{type:"string"},threadId:{type:"string"}}},m={required:["sessionId","msgId"],properties:{sessionId:{type:"string"},msgId:{type:"string"}}},g={required:["action"],properties:{action:{type:"string",enum:["create_agent","list_categories","create_category","update_category","assign_category","rotate_api_key"]},agentName:{type:"string"},introduction:{type:"string"},isMain:{type:"boolean"},agentId:{type:"string"},categoryId:{type:"string"},name:{type:"string"},parentId:{type:"string"},sortOrder:{type:"integer"}}},y={required:["session_id","text"],properties:{event_id:{type:"string"},session_id:{type:"string"},text:{type:"string",maxLength:5e4},quoted_message_id:{type:"string"},is_final:{type:"boolean"}}},d={required:["event_id","status"],properties:{event_id:{type:"string"},status:{type:"string",enum:["responded","canceled","failed"]},msg:{type:"string",maxLength:500}}},c={required:["event_id"],properties:{event_id:{type:"string"},session_id:{type:"string"}}},
|
|
1
|
+
const o={required:["action"],properties:{action:{type:"string",enum:["contact_search","session_search","message_history","message_search"]},id:{type:"string"},keyword:{type:"string",maxLength:200},limit:{type:"integer",minimum:1,maximum:100},offset:{type:"integer",minimum:0},sessionId:{type:"string"},beforeId:{type:"string"}}},a={required:["action"],properties:{action:{type:"string",enum:["create","detail","leave","add_members","remove_members","update_member_role","update_all_members_muted","update_member_speaking","dissolve"]},sessionId:{type:"string"},name:{type:"string",maxLength:128},memberIds:{type:"array",items:{type:"string"},maxItems:100},memberTypes:{type:"array",items:{type:"integer",enum:[1,2]}},memberId:{type:"string"},role:{type:"integer",enum:[1,2]},memberType:{type:"integer"},allMembersMuted:{type:"boolean"},isSpeakMuted:{type:"boolean"},canSpeakWhenAllMuted:{type:"boolean"}}},p={required:["sessionId","content"],properties:{sessionId:{type:"string"},content:{type:"string",maxLength:1e4},msgType:{type:"integer"},quotedMessageId:{type:"string"},threadId:{type:"string"}}},m={required:["sessionId","msgId"],properties:{sessionId:{type:"string"},msgId:{type:"string"}}},g={required:["action"],properties:{action:{type:"string",enum:["create_agent","list_categories","create_category","update_category","assign_category","rotate_api_key"]},agentName:{type:"string"},introduction:{type:"string"},isMain:{type:"boolean"},agentId:{type:"string"},categoryId:{type:"string"},name:{type:"string"},parentId:{type:"string"},sortOrder:{type:"integer"}}},y={required:["session_id","text"],properties:{event_id:{type:"string"},session_id:{type:"string"},text:{type:"string",maxLength:5e4},quoted_message_id:{type:"string"},is_final:{type:"boolean"}}},d={required:["event_id","status"],properties:{event_id:{type:"string"},status:{type:"string",enum:["responded","canceled","failed"]},msg:{type:"string",maxLength:500}}},c={required:["event_id"],properties:{event_id:{type:"string"},session_id:{type:"string"}}},l={required:["session_id","active"],properties:{session_id:{type:"string"},active:{type:"boolean"},event_id:{type:"string"}}},_={required:["action"],properties:{action:{type:"string",enum:["pair_approve","pair_deny","allow_sender","remove_sender","set_policy"]},code:{type:"string"},sender_id:{type:"string"},policy:{type:"string",enum:["allowlist","open","disabled"]}}},f={required:[],properties:{}},C={grix_query:o,grix_group:a,grix_message_send:p,grix_message_unsend:m,grix_admin:g,grix_reply:y,grix_complete:d,grix_event_ack:c,grix_composing:l,grix_access_control:_,grix_status:f};function B(u,t){const e=C[u];if(!e)return{valid:!1,error:`\u672A\u77E5\u5DE5\u5177: ${u}`};for(const i of e.required)if(t[i]===void 0||t[i]===null)return{valid:!1,error:`\u7F3A\u5C11\u5FC5\u586B\u53C2\u6570: ${i}`};for(const[i,r]of Object.entries(t)){if(r==null)continue;const n=e.properties[i];if(!n)continue;const s=$(i,r,n);if(s)return{valid:!1,error:s}}return{valid:!0}}function $(u,t,e){switch(e.type){case"string":if(typeof t!="string")return`\u53C2\u6570 ${u} \u7C7B\u578B\u9519\u8BEF: \u671F\u671B string\uFF0C\u5B9E\u9645 ${typeof t}`;if(e.maxLength!==void 0&&t.length>e.maxLength)return`\u53C2\u6570 ${u} \u8D85\u8FC7\u6700\u5927\u957F\u5EA6 ${e.maxLength}\uFF0C\u5B9E\u9645 ${t.length}`;if(e.enum&&!e.enum.includes(t))return`\u53C2\u6570 ${u} \u503C "${t}" \u4E0D\u5728\u5141\u8BB8\u8303\u56F4 [${e.enum.join(", ")}]`;break;case"integer":if(typeof t!="number"||!Number.isInteger(t))return`\u53C2\u6570 ${u} \u7C7B\u578B\u9519\u8BEF: \u671F\u671B integer\uFF0C\u5B9E\u9645 ${typeof t=="number"?"\u6D6E\u70B9\u6570":typeof t}`;if(e.minimum!==void 0&&t<e.minimum)return`\u53C2\u6570 ${u} \u503C ${t} \u5C0F\u4E8E\u6700\u5C0F\u503C ${e.minimum}`;if(e.maximum!==void 0&&t>e.maximum)return`\u53C2\u6570 ${u} \u503C ${t} \u5927\u4E8E\u6700\u5927\u503C ${e.maximum}`;if(e.enum&&!e.enum.includes(t))return`\u53C2\u6570 ${u} \u503C ${t} \u4E0D\u5728\u5141\u8BB8\u8303\u56F4 [${e.enum.join(", ")}]`;break;case"boolean":if(typeof t!="boolean")return`\u53C2\u6570 ${u} \u7C7B\u578B\u9519\u8BEF: \u671F\u671B boolean\uFF0C\u5B9E\u9645 ${typeof t}`;break;case"array":if(!Array.isArray(t))return`\u53C2\u6570 ${u} \u7C7B\u578B\u9519\u8BEF: \u671F\u671B array\uFF0C\u5B9E\u9645 ${typeof t}`;if(e.maxItems!==void 0&&t.length>e.maxItems)return`\u53C2\u6570 ${u} \u8D85\u8FC7\u6700\u5927\u5143\u7D20\u6570 ${e.maxItems}\uFF0C\u5B9E\u9645 ${t.length}`;if(e.items)for(let i=0;i<t.length;i++){const r=t[i];if(e.items.type==="string"&&typeof r!="string")return`\u53C2\u6570 ${u}[${i}] \u7C7B\u578B\u9519\u8BEF: \u671F\u671B string\uFF0C\u5B9E\u9645 ${typeof r}`;if(e.items.type==="integer"){if(typeof r!="number"||!Number.isInteger(r))return`\u53C2\u6570 ${u}[${i}] \u7C7B\u578B\u9519\u8BEF: \u671F\u671B integer\uFF0C\u5B9E\u9645 ${typeof r}`;if(e.items.enum&&!e.items.enum.includes(r))return`\u53C2\u6570 ${u}[${i}] \u503C ${r} \u4E0D\u5728\u5141\u8BB8\u8303\u56F4 [${e.items.enum.join(", ")}]`}}break}}export{B as validateToolArgs};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "grix-connector",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Connect local AI coding agents (Claude, Codex, Gemini, Qwen, DeepSeek, Cursor, OpenCode, Pi, OpenHuman, Reasonix) to the Grix scheduling platform. Also serves as an OpenClaw plugin for Grix channel transport.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|