grix-connector 1.3.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter/acp/acp-adapter.js +7 -7
- package/dist/adapter/agy/agy-adapter.js +3 -2
- package/dist/adapter/claude/claude-adapter.js +17 -17
- 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/codewhale/codewhale-adapter.js +3 -3
- package/dist/adapter/codex/codex-bridge.js +5 -5
- package/dist/adapter/cursor/cursor-adapter.js +5 -5
- package/dist/adapter/opencode/opencode-adapter.js +4 -4
- package/dist/adapter/openhuman/openhuman-adapter.js +3 -3
- package/dist/adapter/pi/pi-adapter.js +5 -5
- package/dist/core/file-ops/list-files.js +1 -1
- package/dist/default-skills/index.js +1 -1
- package/dist/log.js +2 -2
- package/dist/manager.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/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
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import{EventEmitter as
|
|
2
|
-
`,"utf8"),n.info("opencode-adapter",`MCP config injected into ${t}`)}catch(e){n.warn("opencode-adapter",`Failed to inject MCP tools (non-fatal): ${e instanceof Error?e.message:String(e)}`)}}async handleLocalAction(e){const{action_type:s}=e;return s==="exec_approve"||s==="exec_reject"||s==="permission_approve"||s==="permission_reject"?this.handlePermissionAction(e):{handled:!1,kind:""}}bindSession(e,s){if(n.info("opencode-adapter",`bindSession: ${e} \u2192 ${s}`),!this.sessions.has(e))this.sessions.set(e,{ocSessionId:"",cwd:s});else{const t=this.sessions.get(e);t.cwd=s}}async spawnAndWait(e,s){const t=this.resolveCwd();try{if(!(await
|
|
3
|
-
`)){const R=l.match(/opencode server listening on (https?:\/\/[^\s]+)/);if(R){clearTimeout(
|
|
1
|
+
import{EventEmitter as I}from"node:events";import{stat as w}from"node:fs/promises";import{existsSync as y,mkdirSync as b,readFileSync as E,writeFileSync as $}from"node:fs";import{join as m,resolve as P,dirname as A}from"node:path";import{homedir as _}from"node:os";import{fileURLToPath as C}from"node:url";import{resolveCommandPath as O,spawnCommand as M,killProcessGroup as T,hasChildProcesses as F}from"../../core/runtime/spawn.js";import{InternalApiServer as B}from"../../core/mcp/internal-api-server.js";import{syncDefaultSkillsToDir as j}from"../../default-skills/index.js";import{buildSimpleProbeReport as D}from"../shared/probe-util.js";import{OpenCodeTransport as N}from"./opencode-transport.js";import{log as n}from"../../core/log/index.js";import{splitTextForAibotProtocol as S}from"../../core/protocol/index.js";class H 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 L=200,U=2e3,J=12e4,G=6e5,x=3e4,q="127.0.0.1",X=0,W=1e3;class ae extends I{type="opencode";config;callbacks;options;process=null;transport=new N;alive=!1;stopped=!1;internalApi=null;sessions=new Map;activeRun=null;completedEvents=new Set;clientMsgSeq=0;idleTimer=null;pendingPermissions=new Map;permissionHandler=null;constructor(e,s,t){super(),this.config=e,this.callbacks=s,this.options=t??{}}async start(){await this.startInternalApiAndInjectMcp();const e=this.options.hostname??q,s=this.options.port??X,t=await this.spawnAndWait(e,s),i=this.resolveCwd();await this.transport.connect(t,i),this.transport.on("event",o=>this.handleSseEvent(o)),n.info("opencode-adapter",`Ready (pid=${this.process?.pid}, url=${t})`)}async stop(){if(this.stopped=!0,this.alive=!1,this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.transport.disconnect(),this.internalApi&&(await this.internalApi.stop(),this.internalApi=null),this.process){const e=this.process;try{T(e,"SIGTERM")}catch{}const s=setTimeout(()=>{try{T(e,"SIGKILL")}catch{}},5e3);e.on("exit",()=>clearTimeout(s)),this.process=null}}isAlive(){return this.alive}async createSession(e){const s=e.cwd??this.resolveCwd(),t=await this.transport.createSession({title:`grix-${Date.now()}`});return this.sessions.set(t.id,{ocSessionId:t.id,cwd:s}),n.info("opencode-adapter",`Created OC session ${t.id} for cwd=${s}`),t.id}async resumeSession(e,s){const t=this.sessions.get(e);if(t?.ocSessionId)try{await this.transport.getSession(t.ocSessionId)}catch{n.warn("opencode-adapter",`OC session ${t.ocSessionId} gone, will create new on next prompt`),t.ocSessionId=""}}async destroySession(e){try{await this.transport.deleteSession(e)}catch{}this.sessions.delete(e)}sendPrompt(e){const s=new H(e.adapterSessionId);return this.sendOcPrompt(e.adapterSessionId,this.buildPromptText(e)).catch(t=>{s.emitError(t instanceof Error?t:new Error(String(t)))}),s}async cancel(e){if(this.activeRun)try{await this.transport.abortSession(this.activeRun.ocSessionId)}catch{}}deliverInboundEvent(e){const{event_id:s,session_id:t,content:i}=e;if(this.completedEvents.has(s)){n.info("opencode-adapter",`Dropping duplicate event ${s}`),this.callbacks.sendEventAck(s,t),this.callbacks.sendEventResult(s,"responded");return}if(!this.alive){n.warn("opencode-adapter",`Dropping event ${s}: process not alive`),this.callbacks.sendEventAck(s,t),this.callbacks.sendEventResult(s,"failed","Agent process not running");return}this.activeRun&&this.activeRun.eventId!==s&&(n.info("opencode-adapter",`steer: ${this.activeRun.eventId} -> ${s}`),this.flushTextBuffer(),this.callbacks.sendEventResult(this.activeRun.eventId,"canceled","steered to new event"),this.clearRun()),n.info("opencode-adapter",`prompt: event=${s} session=${t}`),this.callbacks.sendEventAck(s,t),this.startRun(s,t),this.startComposing(t),this.resetIdleTimer();const o=this.buildPromptTextFromEvent(e);this.sendOcPrompt(t,o).catch(r=>{n.error("opencode-adapter",`prompt_async failed: ${r}`),this.finishRun("failed",String(r))})}deliverStopEvent(e,s){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(){if(!this.internalApi)return null;const e=P(C(import.meta.url),"../../../mcp/acp-mcp-server.js");return{name:"grix-connector-tools",command:process.execPath,args:[e,"--api-url",this.internalApi.url]}}async hasBackgroundWork(){const e=this.process?.pid;return e?F(e,[e]):!1}async probe(e){const s=this.getStatus();return D(this.config.command||"opencode",{alive:s.alive,busy:s.busy,started:!!this.process},e)}async startInternalApiAndInjectMcp(){try{this.internalApi=new B,this.internalApi.setInvokeHandler(async(p,h)=>this.callbacks.agentInvoke(p,h)),await this.internalApi.start(0),n.info("opencode-adapter",`Internal API started at ${this.internalApi.url}`);const e=this.getMcpConfig(),s=process.env.XDG_CONFIG_HOME||m(_(),".config"),t=m(s,"opencode","opencode.json");b(A(t),{recursive:!0});let i={};try{y(t)&&(i=JSON.parse(E(t,"utf8")))}catch{}const o=i.mcp&&typeof i.mcp=="object"?i.mcp:{};o[e.name]={type:"local",command:[e.command,...e.args??[]],enabled:!0},i.mcp=o,$(t,`${JSON.stringify(i,null,2)}
|
|
2
|
+
`,"utf8"),n.info("opencode-adapter",`MCP config injected into ${t}`);const r=m(s,"opencode","skills"),c=j(r);c.length>0&&n.info("opencode-adapter",`Synced connector skills to ${r}: [${c.join(", ")}]`)}catch(e){n.warn("opencode-adapter",`Failed to inject MCP tools (non-fatal): ${e instanceof Error?e.message:String(e)}`)}}async handleLocalAction(e){const{action_type:s}=e;return s==="exec_approve"||s==="exec_reject"||s==="permission_approve"||s==="permission_reject"?this.handlePermissionAction(e):{handled:!1,kind:""}}bindSession(e,s){if(n.info("opencode-adapter",`bindSession: ${e} \u2192 ${s}`),!this.sessions.has(e))this.sessions.set(e,{ocSessionId:"",cwd:s});else{const t=this.sessions.get(e);t.cwd=s}}async spawnAndWait(e,s){const t=this.resolveCwd();try{if(!(await w(t)).isDirectory())throw new Error(`Bound path is not a directory: ${t}`)}catch(i){throw String(i?.code??"")==="ENOENT"?new Error(`Bound directory does not exist: ${t}. Please rebind with /grix open <valid-directory>.`):i}return new Promise((i,o)=>{const r=this.config.command||"opencode",p=[...this.config.args??["serve"],`--hostname=${e}`,`--port=${s}`],h={...process.env,...this.config.env},u={};if(this.options.model){const[a,...l]=this.options.model.split("/");a&&l.length>0&&(u.agents={coder:{model:l.join("/"),maxTokens:16e3}})}this.options.permissionPolicy==="fullAuto"&&(u.permission={edit:"allow",bash:"allow",webfetch:"allow",doom_loop:"allow",external_directory:"allow"}),Object.keys(u).length>0&&(h.OPENCODE_CONFIG_CONTENT=JSON.stringify(u));const v=O(r,typeof h.PATH=="string"?h.PATH:void 0);n.info("opencode-adapter",`Spawning: ${v} ${p.join(" ")} (cwd=${t})`),this.process=M(v,p,{env:h,cwd:t}).process;let g="",d=!1;const f=setTimeout(()=>{d||(d=!0,o(new Error(`opencode serve did not start after ${x/1e3}s`)))},x);this.process.stdout?.on("data",a=>{if(g+=a.toString(),!d)for(const l of g.split(`
|
|
3
|
+
`)){const R=l.match(/opencode server listening on (https?:\/\/[^\s]+)/);if(R){clearTimeout(f),d=!0,this.alive=!0,i(R[1]);return}}}),this.process.stderr?.on("data",a=>{const l=a.toString().trim();l&&n.info("opencode-adapter",`[stderr] ${l}`)}),this.process.on("error",a=>{n.error("opencode-adapter",`Spawn error: ${a.message}`),clearTimeout(f),this.alive=!1,this.transport.disconnect(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed",`Spawn error: ${a.message}`),this.clearRun()),d?this.stopped||this.emit("exit",1):(d=!0,o(a))}),this.process.on("exit",a=>{n.info("opencode-adapter",`Process exited (code=${a})`),clearTimeout(f),this.alive=!1,this.transport.disconnect(),this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed",`Process exited (code=${a})`),this.clearRun()),d?this.stopped||this.emit("exit",a??1):(d=!0,o(new Error(`opencode serve exited with code ${a}`)))})})}async ensureOcSession(e){const s=this.sessions.get(e);if(s?.ocSessionId)try{return await this.transport.getSession(s.ocSessionId),s.ocSessionId}catch{n.warn("opencode-adapter",`OC session ${s.ocSessionId} gone, creating new`),s.ocSessionId=""}const t=s?.cwd??this.resolveCwd(),i=await this.transport.createSession({title:`grix-${e.slice(-8)}`});return this.sessions.set(e,{ocSessionId:i.id,cwd:t}),n.info("opencode-adapter",`Created OC session ${i.id} for aibot=${e}`),i.id}async sendOcPrompt(e,s){const t=await this.ensureOcSession(e);this.activeRun&&(this.activeRun.ocSessionId=t),await this.transport.sendPromptAsync(t,{parts:[{type:"text",text:s}]})}handleSseEvent(e){if(!this.stopped)switch(this.resetIdleTimer(),e.type){case"message.part.updated":{if(!this.activeRun)break;const s=e.part,t=e.delta;s.type==="text"?t?this.appendText(t):s.text&&this.appendText(s.text):s.type==="reasoning"?t&&this.callbacks.sendThinking(this.activeRun.eventId,this.activeRun.sessionId,t):s.type==="tool"&&this.handleToolPartUpdate(s,t);break}case"message.updated":{if(!this.activeRun)break;const s=e.info;if(s.role==="assistant"){const t=s;t.error&&n.warn("opencode-adapter",`Message error: ${JSON.stringify(t.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 s=e.error,t=s?typeof s=="object"&&"message"in s?s.message:JSON.stringify(s):"unknown session error";n.error("opencode-adapter",`Session error: ${t}`),this.flushTextBuffer(),this.finishRun("failed",t);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,s){if(!this.activeRun)return;const t=e.state;switch(t.status){case"pending":case"running":{this.flushTextBuffer();const i=JSON.stringify(t.input??{});this.callbacks.sendToolUse(this.activeRun.eventId,this.activeRun.sessionId,e.tool,i);break}case"completed":{const i=t.output??"";this.callbacks.sendToolResult(this.activeRun.eventId,this.activeRun.sessionId,e.tool,i);break}case"error":{const i=t.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:s,sessionId:t,ocSessionId:i}=this.activeRun;if(this.options.permissionPolicy==="fullAuto"){this.transport.respondPermission(i,e.id,"always").catch(r=>{n.warn("opencode-adapter",`Auto-approve failed: ${r}`)});return}this.stopIdleTimer(),this.pendingPermissions.set(s,{permissionId:e.id,ocSessionId:i});const o=JSON.stringify(e.metadata??{});this.callbacks.sendToolUse(s,t,e.type,o)}handlePermissionAction(e){if(!this.activeRun)return{handled:!1,kind:""};const s=this.pendingPermissions.get(this.activeRun.eventId);if(!s)return{handled:!1,kind:""};const{permissionId:t,ocSessionId:i}=s,r=e.action_type==="exec_approve"||e.action_type==="permission_approve"?"once":"reject";return this.pendingPermissions.delete(this.activeRun.eventId),this.transport.respondPermission(i,t,r).then(()=>{n.info("opencode-adapter",`Permission ${r}: ${t}`),this.resetIdleTimer()}).catch(c=>{n.warn("opencode-adapter",`Permission response failed: ${c}`)}),{handled:!0,kind:"permission"}}startRun(e,s){this.activeRun={eventId:e,sessionId:s,ocSessionId:"",chunkSeq:0,clientMsgId:`oc_${++this.clientMsgSeq}_${Date.now()}`,textBuffer:"",flushTimer:null}}finishRun(e,s){const t=this.activeRun;if(!t)return;if(this.completedEvents.add(t.eventId),this.completedEvents.size>W){const c=[...this.completedEvents].slice(-500);this.completedEvents=new Set(c)}this.activeRun=null,this.stopComposing(),this.stopIdleTimer();const i=++t.chunkSeq,o=t.clientMsgId;e==="failed"&&s&&this.callbacks.sendRunError(t.eventId,t.sessionId,s);const r=()=>{this.callbacks.sendFinalStreamChunkReliable?this.callbacks.sendFinalStreamChunkReliable(t.eventId,t.sessionId,i,o).then(()=>{this.callbacks.sendEventResult(t.eventId,e,s)}).catch(()=>{this.callbacks.sendStreamChunk(t.eventId,t.sessionId,"",i,!0,o),this.callbacks.sendEventResult(t.eventId,e,s)}):(this.callbacks.sendStreamChunk(t.eventId,t.sessionId,"",i,!0,o),this.callbacks.sendEventResult(t.eventId,e,s))};if(t.textBuffer){this.stopTextFlush();for(const c of S(t.textBuffer))t.chunkSeq++,this.callbacks.sendStreamChunk(t.eventId,t.sessionId,c,t.chunkSeq,!1,t.clientMsgId);t.textBuffer=""}r(),this.emit("eventDone",t.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>=U){this.flushTextBuffer();return}this.scheduleTextFlush()}}scheduleTextFlush(){!this.activeRun||this.activeRun.flushTimer||(this.activeRun.flushTimer=setTimeout(()=>{this.activeRun&&(this.activeRun.flushTimer=null),this.flushTextBuffer()},L))}flushTextBuffer(){if(this.stopTextFlush(),!(!this.activeRun||!this.activeRun.textBuffer)){for(const e of S(this.activeRun.textBuffer))this.activeRun.chunkSeq++,this.callbacks.sendStreamChunk(this.activeRun.eventId,this.activeRun.sessionId,e,this.activeRun.chunkSeq,!1,this.activeRun.clientMsgId);this.activeRun.textBuffer=""}}stopTextFlush(){this.activeRun?.flushTimer&&(clearTimeout(this.activeRun.flushTimer),this.activeRun.flushTimer=null)}startComposing(e){}stopComposing(){}resetIdleTimer(){if(this.stopIdleTimer(),this.stopped||!this.activeRun)return;const e=this.pendingPermissions.has(this.activeRun.eventId)?G:J;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 s=e.text;return e.contextMessages&&e.contextMessages.length>0&&(s=e.contextMessages.map(i=>`[context] ${i.senderId}: ${i.content}`).join(`
|
|
4
4
|
`)+`
|
|
5
5
|
|
|
6
6
|
`+s),s}buildPromptTextFromEvent(e){let s=e.content||"";if(e.context_messages_json)try{const t=JSON.parse(e.context_messages_json);Array.isArray(t)&&t.length>0&&(s=t.map(o=>`[context] ${o.sender_id??"unknown"}: ${o.content}`).join(`
|
|
7
7
|
`)+`
|
|
8
8
|
|
|
9
|
-
`+s)}catch{}return s}}export{
|
|
9
|
+
`+s)}catch{}return s}}export{ae as OpenCodeAdapter};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import{EventEmitter as m}from"node:events";import{stat as R}from"node:fs/promises";import{mkdirSync as I}from"node:fs";import{homedir as
|
|
2
|
-
`);
|
|
1
|
+
import{EventEmitter as m}from"node:events";import{stat as R}from"node:fs/promises";import{mkdirSync as I}from"node:fs";import{homedir as u}from"node:os";import{join as p,resolve as _}from"node:path";import{fileURLToPath as g}from"node:url";import{randomUUID as k}from"node:crypto";import{resolveCommandPath as E,spawnCommand as x,killProcessGroup as f,hasChildProcesses as w}from"../../core/runtime/spawn.js";import{InternalApiServer as S}from"../../core/mcp/internal-api-server.js";import{syncDefaultSkillsToDir as b}from"../../default-skills/index.js";import{buildSimpleProbeReport as y}from"../shared/probe-util.js";import{OpenHumanTransport as $,readBearerToken as A}from"./openhuman-transport.js";import{log as n}from"../../core/log/index.js";import{splitTextForAibotProtocol as C}from"../../core/protocol/index.js";class L extends m{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 P=200,F=2e3,D=12e4,M=500,v=3e4,N=7788,B="127.0.0.1";class Z extends m{type="openhuman";config;callbacks;options;process=null;transport=new $;alive=!1;stopped=!1;internalApi=null;clientId;activeRun=null;completedEvents=new Set;clientMsgSeq=0;idleTimer=null;constructor(t,e,s){super(),this.config=t,this.callbacks=e,this.options=s??{},this.clientId=`grix-connector-${Date.now()}`}async start(){await this.startInternalApiAndInjectMcp();const t=this.options.host??B,e=this.options.port??N;await this.spawnProcess(t,e),await this.waitForServerReady(t,e);const s=this.resolveWorkspaceDir(),i=await A(s),r=`http://${t}:${e}`;if(await this.transport.connect(r,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.internalApi&&(await this.internalApi.stop(),this.internalApi=null),this.process){const t=this.process;try{f(t,"SIGTERM")}catch{}const e=setTimeout(()=>{try{f(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 L(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,r=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,r).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(){if(!this.internalApi)return null;const t=_(g(import.meta.url),"../../../mcp/acp-mcp-server.js");return{name:"grix-connector-tools",command:process.execPath,args:[t,"--api-url",this.internalApi.url]}}async hasBackgroundWork(){const t=this.process?.pid;return t?w(t,[t]):!1}async probe(t){const e=this.getStatus();return y(this.config.command||"openhuman-core",{alive:e.alive,busy:e.busy,started:!!this.process},t)}async startInternalApiAndInjectMcp(){try{this.internalApi=new S,this.internalApi.setInvokeHandler(async(a,d)=>this.callbacks.agentInvoke(a,d)),await this.internalApi.start(0),n.info("openhuman-adapter",`Internal API started at ${this.internalApi.url}`);const t=this.getMcpConfig(),e=p(u(),".openhuman","users","local","workspace","mcp_clients"),s=p(e,"mcp_clients.db");I(e,{recursive:!0});const{execFileSync:i}=await import("node:child_process"),r=k(),o=JSON.stringify(t.args),l=["CREATE TABLE IF NOT EXISTS mcp_servers (server_id TEXT PRIMARY KEY, qualified_name TEXT NOT NULL, display_name TEXT NOT NULL, description TEXT, icon_url TEXT, command_kind TEXT NOT NULL DEFAULT 'node', command TEXT NOT NULL, args_json TEXT NOT NULL DEFAULT '[]', env_keys_json TEXT NOT NULL DEFAULT '[]', config_json TEXT, installed_at INTEGER NOT NULL, last_connected_at INTEGER);","DELETE FROM mcp_servers WHERE qualified_name = 'grix-connector-tools';",`INSERT INTO mcp_servers (server_id, qualified_name, display_name, description, command_kind, command, args_json, installed_at) VALUES ('${r}', 'grix-connector-tools', 'Grix Connector Tools', 'Grix platform query and management tools', 'node', '${t.command}', '${o.replace(/'/g,"''")}', ${Date.now()});`].join(`
|
|
2
|
+
`);i("sqlite3",[s,l],{timeout:1e4,stdio:"ignore"}),n.info("openhuman-adapter",`MCP server registered in SQLite: ${s}`);const c=p(u(),".openhuman","skills"),h=b(c);h.length>0&&n.info("openhuman-adapter",`Synced connector skills to ${c}: [${h.join(", ")}]`)}catch(t){n.warn("openhuman-adapter",`Failed to inject MCP tools (non-fatal): ${t instanceof Error?t.message:String(t)}`)}}async spawnProcess(t,e){const s=this.config.command||"openhuman-core",i=E(s,typeof process.env.PATH=="string"?process.env.PATH:void 0),r=this.config.args??[],l=r.includes("run")||r.includes("serve")?r:["run","--host",t,"--port",String(e),...r],c={...process.env,...this.config.env},h=this.resolveCwd();n.info("openhuman-adapter",`Spawning: ${i} ${l.join(" ")}`);try{if(!(await R(h)).isDirectory())throw new Error(`Bound path is not a directory: ${h}`)}catch(a){throw String(a?.code??"")==="ENOENT"?new Error(`Bound directory does not exist: ${h}. Please rebind with /grix open <valid-directory>.`):a}try{this.process=x(i,l,{env:c,cwd:h}).process}catch(a){throw n.error("openhuman-adapter",`Spawn threw: ${a}`),this.alive=!1,a}this.process.on("error",a=>{n.error("openhuman-adapter",`Spawn error: ${a.message}`),this.alive=!1,this.transport.disconnect(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed",`Spawn error: ${a.message}`),this.clearRun()),this.stopped||this.emit("exit",1)}),this.process.on("exit",a=>{n.info("openhuman-adapter",`Process exited (code=${a})`),this.alive=!1,this.transport.disconnect(),this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed",`Process exited (code=${a})`),this.clearRun()),this.stopped||this.emit("exit",a??1)}),this.process.stderr?.on("data",a=>{const d=a.toString().trim();d&&n.info("openhuman-adapter",`[stderr] ${d}`)}),this.alive=!0}async waitForServerReady(t,e){const s=`http://${t}:${e}/health`,i=Date.now()+v;for(;Date.now()<i;){try{if((await fetch(s,{signal:AbortSignal.timeout(2e3)})).ok)return}catch{}await new Promise(r=>setTimeout(r,M))}throw new Error(`openhuman-core did not become ready at ${t}:${e} after ${v/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 c=0;c<500;c++)o.next();const l=[...this.completedEvents].slice(-500);this.completedEvents=new Set(l)}this.activeRun=null,this.emit("eventDone",s.eventId),this.stopComposing(),this.stopIdleTimer();const i=++s.chunkSeq,r=s.clientMsgId;t==="failed"&&e&&this.callbacks.sendRunError(s.eventId,s.sessionId,e),this.callbacks.sendFinalStreamChunkReliable?this.callbacks.sendFinalStreamChunkReliable(s.eventId,s.sessionId,i,r).then(()=>{this.callbacks.sendEventResult(s.eventId,t,e)}).catch(()=>{this.callbacks.sendStreamChunk(s.eventId,s.sessionId,"",i,!0,r),this.callbacks.sendEventResult(s.eventId,t,e)}):(this.callbacks.sendStreamChunk(s.eventId,s.sessionId,"",i,!0,r),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>=F){this.flushTextBuffer();return}this.scheduleTextFlush()}}scheduleTextFlush(){!this.activeRun||this.activeRun.flushTimer||(this.activeRun.flushTimer=setTimeout(()=>{this.activeRun&&(this.activeRun.flushTimer=null),this.flushTextBuffer()},P))}flushTextBuffer(){if(this.stopTextFlush(),!(!this.activeRun||!this.activeRun.textBuffer)){for(const t of C(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){}stopComposing(){}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)},D))}stopIdleTimer(){this.idleTimer&&(clearTimeout(this.idleTimer),this.idleTimer=null)}resolveWorkspaceDir(){return this.options.workspaceDir?this.options.workspaceDir.replace(/^~/,u()):p(u(),".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(`
|
|
3
3
|
`)+`
|
|
4
4
|
|
|
5
5
|
`+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(r=>`[context] ${r.sender_id??"unknown"}: ${r.content}`).join(`
|
|
6
6
|
`)+`
|
|
7
7
|
|
|
8
|
-
`+e)}catch{}return e}}export{
|
|
8
|
+
`+e)}catch{}return e}}export{Z as OpenHumanAdapter};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import{EventEmitter 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()}async startInternalApi(){try{this.internalApi=new
|
|
1
|
+
import{EventEmitter as u}from"node:events";import{stat as v}from"node:fs/promises";import{mkdirSync as g,writeFileSync as S,unlinkSync as I,statSync as x}from"node:fs";import{join as h,resolve as b}from"node:path";import{fileURLToPath as y}from"node:url";import{tmpdir as T,homedir as k}from"node:os";import{resolveCommandPath as E,spawnCommand as w,killProcessGroup as m,hasChildProcesses as _}from"../../core/runtime/spawn.js";import{InternalApiServer as P}from"../../core/mcp/internal-api-server.js";import{syncDefaultSkillsToDir as $}from"../../default-skills/index.js";import{PiTransport as A}from"./pi-transport.js";import{log as o}from"../../core/log/index.js";import{scanSkills as C}from"../claude/skill-scanner.js";import{SessionBindingStore as R}from"../../core/persistence/session-binding-store.js";import{splitTextForAibotProtocol as M}from"../../core/protocol/index.js";import{buildSimpleProbeReport as B}from"../shared/probe-util.js";class F extends u{adapterSessionId;constructor(t){super(),this.adapterSessionId=t}emitDone(t){this.emit("done",t)}emitError(t){if(this.listenerCount("error")===0){o.warn("pi-adapter",`Prompt handle error (no listeners): ${t.message}`);return}this.emit("error",t)}async cancel(){}}const D=12e4,G=500,j=2e3;class f extends u{type="pi";config;callbacks;process=null;transport=new A;alive=!1;stopped=!1;internalApi=null;mcpConfigPath=null;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 R?s.bindingStore:null,this.bindingStore&&this.aibotSessionId&&(this.piSessionPath=this.bindingStore.getPiSessionPath(this.aibotSessionId)??null)}async start(){await this.startInternalApi(),await this.spawnPi(),await this.ensureSessionReady(),o.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.internalApi&&(await this.internalApi.stop(),this.internalApi=null),this.mcpConfigPath){try{I(this.mcpConfigPath)}catch{}this.mcpConfigPath=null}if(this.process){const t=this.process;try{m(t,"SIGTERM")}catch{}const e=setTimeout(()=>{try{m(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 o.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 F(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&&(o.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=>{o.error("pi-adapter",`steer failed: ${a}`),this.callbacks.sendEventResult(e,"failed",String(a))})):(o.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||(o.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=>{o.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){o.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(){if(!this.internalApi)return null;const t=b(y(import.meta.url),"../../../mcp/acp-mcp-server.js");return{name:"grix-connector-tools",command:process.execPath,args:[t,"--api-url",this.internalApi.url]}}async hasBackgroundWork(){const t=this.process?.pid;return t?_(t,[t]):!1}async probe(t){const e=this.getStatus();return{...await B(this.config.command||"pi",{alive:e.alive,busy:e.busy,started:!!this.process},t),session:this.probeSessionRecord()}}probeSessionRecord(){const t=this.piSessionPath;if(!t)return{recordPath:null,lastActivityMs:null,freshMs:null};try{const e=x(t);return{recordPath:t,lastActivityMs:e.mtimeMs,freshMs:Date.now()-e.mtimeMs}}catch{return{recordPath:t,lastActivityMs:null,freshMs:null}}}getSupportedCommands(){return[{name:"model",description:"List or set model",args:"[provider:model_id]"},{name:"interrupt",description:"Interrupt current run"},{name:"status",description:"Show session status"},{name:"skills",description:"List available skills"}]}async execCommand(t,e,s){try{if(!this.alive)return{status:"failed",message:"Pi process not running"};switch(t){case"model":{const i=e.trim();if(i){const c=i.indexOf(":");if(c<1)return{status:"failed",message:"Format: provider:model_id"};const r=i.slice(0,c),d=i.slice(c+1);if(!d)return{status:"failed",message:"Format: provider:model_id"};const p=await this.transport.send("set_model",{provider:r,modelId:d});return p?.success?{status:"ok",message:`Model set to ${r}:${d}`}:{status:"failed",message:`Failed to set model: ${p?.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(r=>` ${r.provider??"unknown"}:${r.id} (${r.name??r.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=C({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()}async startInternalApi(){try{this.internalApi=new P,this.internalApi.setInvokeHandler(async(n,a)=>this.callbacks.agentInvoke(n,a)),await this.internalApi.start(0),o.info("pi-adapter",`Internal API started at ${this.internalApi.url}`);const t=this.getMcpConfig(),e=h(T(),"grix-pi-mcp");g(e,{recursive:!0}),this.mcpConfigPath=h(e,`mcp-${process.pid}-${Date.now()}.json`),S(this.mcpConfigPath,JSON.stringify({mcpServers:{[t.name]:{command:t.command,args:t.args,directTools:!0,lifecycle:"eager"}}}),"utf8"),o.info("pi-adapter",`MCP config written to ${this.mcpConfigPath}`);const s=h(k(),".pi","agent","skills"),i=$(s);i.length>0&&o.info("pi-adapter",`Synced connector skills to ${s}: [${i.join(", ")}]`)}catch(t){o.warn("pi-adapter",`Failed to start MCP tools (non-fatal): ${t instanceof Error?t.message:String(t)}`)}}async spawnPi(){const t=this.config.command||"pi",e=E(t,typeof process.env.PATH=="string"?process.env.PATH:void 0),s=this.config.args??[],n=s.some(r=>r.startsWith("--mode"))?[...s]:["--mode","rpc",...s];this.mcpConfigPath&&!n.some(r=>r==="--mcp-config")&&n.push("--mcp-config",this.mcpConfigPath);const a={...process.env,...this.config.env},c=this.resolveCwd();o.info("pi-adapter",`Spawning: ${e} ${n.join(" ")}`);try{if(!(await v(c)).isDirectory())throw new Error(`Bound path is not a directory: ${c}`)}catch(r){throw String(r?.code??"")==="ENOENT"?new Error(`Bound directory does not exist: ${c}. Please rebind with /grix open <valid-directory>.`):r}try{this.process=w(e,n,{env:a,cwd:c}).process}catch(r){throw o.error("pi-adapter",`PI spawn threw: ${r}`),this.alive=!1,r}this.process.on("error",r=>{o.error("pi-adapter",`Spawn error: ${r.message}`),this.alive=!1,this.transport.close(),this.activeEventId&&(this.callbacks.sendEventResult(this.activeEventId,"failed",`Spawn error: ${r.message}`),this.clearActive()),this.stopped||this.emit("exit",1)}),this.process.on("exit",r=>{o.info("pi-adapter",`PI process exited (code=${r})`),this.alive=!1,this.transport.close(),this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.activeEventId&&this.callbacks.sendEventResult(this.activeEventId,"failed",`PI process exited (code=${r})`),this.isStreaming=!1,this.clearActive(),this.stopped||this.emit("exit",r??1)}),this.process.stderr?.on("data",r=>{const d=r.toString().trim();d&&o.info("pi-adapter",`[pi stderr] ${d}`)}),this.transport.on("event",r=>this.handlePiEvent(r)),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=N(s.result);i&&this.callbacks.sendToolResult(this.activeEventId,this.activeSessionId,s.toolName,i);break}case"agent_end":{if(o.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&&(o.info("pi-adapter",`agent_start event=${this.activeEventId}`),this.isStreaming=!0);break}default:break}}appendText(t){if(this.textBuffer+=t,this.textBuffer.length>=j){this.flushTextBuffer();return}this.scheduleTextFlush()}scheduleTextFlush(){this.textFlushTimer||(this.textFlushTimer=setTimeout(()=>{this.textFlushTimer=null,this.flushTextBuffer()},G))}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 M(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(){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"),
|
|
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"),o.info("pi-adapter",`event completed (reliable) event=${t}`)}).catch(n=>{o.error("pi-adapter",`finalStreamChunk ACK failed event=${t}: ${n}`),this.callbacks.sendStreamChunk(t,e,"",i,!0,s),this.callbacks.sendEventResult(t,"responded"),o.info("pi-adapter",`event completed (fallback) event=${t}`)}):(this.callbacks.sendStreamChunk(t,e,"",i,!0,s),this.callbacks.sendEventResult(t,"responded"),o.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}o.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;o.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)},f.DONE_GUARD_MS))}cancelDoneGuard(){this.doneGuardTimer&&(clearTimeout(this.doneGuardTimer),this.doneGuardTimer=null)}resetIdleTimer(){this.stopIdleTimer(),!(this.stopped||!this.isStreaming)&&(this.idleTimer=setTimeout(()=>{o.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)},D))}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&&o.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 N(l){if(!l||typeof l!="object")return"";const t=l.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{f as PiAdapter};
|
|
@@ -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 i of c){if(!p&&i.name.startsWith("."))continue;const t=l(a,i.name),e={id:t,name:i.name,is_directory:i.isDirectory()};try{if(i.isDirectory()){const o=await m(t);e.modified_at=o.mtime.toISOString()}else{const o=await m(t);e.size=o.size,e.modified_at=o.mtime.toISOString(),e.mime_type=n(i.name)}}catch{}s.push(e)}return s.sort((i,t)=>i.is_directory!==t.is_directory?i.is_directory?-1:1:i.name.localeCompare(t.name)),s}export{f as listFiles,n as resolveMimeType};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{readFileSync as
|
|
1
|
+
import{readFileSync as m,readdirSync as c,existsSync as u,mkdirSync as d,cpSync as p,writeFileSync as k,rmSync as y}from"node:fs";import{join as t,dirname as h}from"node:path";import{homedir as g}from"node:os";import{fileURLToPath as S}from"node:url";import{parseSkillFrontmatter as D}from"../adapter/claude/skill-scanner.js";import{resolveRuntimePaths as x}from"../core/config/index.js";import{log as a}from"../core/log/index.js";const l=h(S(import.meta.url)),f=".grix-managed";function v(){const e=[];let i;try{i=c(l,{withFileTypes:!0,encoding:"utf8"})}catch{return e}for(const r of i){if(!r.isDirectory())continue;const n=t(l,r.name,"SKILL.md");try{const s=m(n,"utf-8"),o=D(s);o?.name&&e.push({name:o.name,description:o.description,trigger:o.trigger,source:"connector"})}catch{}}return e}function K(e){const i=[];let r;try{r=c(l,{withFileTypes:!0,encoding:"utf8"})}catch{return i}for(const n of r){if(!n.isDirectory())continue;const s=t(l,n.name);if(u(t(s,"SKILL.md")))try{d(e,{recursive:!0});const o=t(e,n.name);p(s,o,{recursive:!0}),k(t(o,f),"","utf8"),i.push(n.name)}catch{}}return i}function F(){const e=g(),i=process.env.XDG_CONFIG_HOME||t(e,".config");return[t(x().dataDir,"claude-plugin","skills"),t(e,".codex","skills"),t(e,".gemini","skills"),t(e,".qwen","skills"),t(e,".pi","agent","skills"),t(e,".codewhale","skills"),t(i,"opencode","skills"),t(e,".cursor","skills"),t(e,".openhuman","skills"),t(e,".kiro","skills"),t(e,".reasonix","skills")]}function M(){const e=[];for(const i of F()){let r;try{r=c(i,{withFileTypes:!0,encoding:"utf8"})}catch{continue}for(const n of r){if(!n.isDirectory())continue;const s=t(i,n.name);if(u(t(s,f)))try{y(s,{recursive:!0,force:!0}),e.push(s)}catch{}}}return e.length>0&&a.info("default-skills",`Cleaned ${e.length} projected skill dir(s)`),e}function R(){const e=v();if(e.length===0){a.warn("default-skills","No connector system skills found \u2014 dist/default-skills/ may be missing SKILL.md files");return}a.info("default-skills",`Connector system skills available: [${e.map(i=>i.name).join(", ")}]`)}export{M as cleanupProjectedSkills,R as logDefaultSkillsCheck,v as scanDefaultSkills,K as syncDefaultSkillsToDir};
|
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,r,...
|
|
1
|
+
import{createWriteStream as g,mkdirSync as l,existsSync as f}from"node:fs";import{join as i}from"node:path";import{homedir as m}from"node:os";const n=i(m(),".grix"),s={base:n,config:i(n,"config"),log:i(n,"log"),data:i(n,"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),r=i(s.log,`grix-acp-${o}.log`);a=g(r,{flags:"a"})}function c(){return new Date().toISOString().slice(11,19)}const u={info(o,r,...t){const e=`${c()} [${o}] ${r}${t.length?" "+t.map(String).join(" "):""}`;console.log(e),a?.write(e+`
|
|
2
|
+
`)},error(o,r,...t){const e=`${c()} [${o}] ERROR ${r}${t.length?" "+t.map(String).join(" "):""}`;console.error(e),a?.write(e+`
|
|
3
3
|
`)}};export{s as GRIX_PATHS,S as ensureGrixDirs,$ as initLogger,u as log};
|
package/dist/manager.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{readFileSync as H,readdirSync as Q,writeFileSync as F}from"node:fs";import{join as x}from"node:path";import{AgentInstance as S}from"./bridge/bridge.js";import{GRIX_PATHS as A,log as c}from"./core/log/index.js";import{resolveClientVersion as B}from"./core/util/client-version.js";import{UpgradeChecker as L}from"./core/upgrade/upgrade-checker.js";import{AgentGlobalConfigStore as R}from"./core/persistence/agent-global-config-store.js";import{scanSkills as z,dedupeSkills as G}from"./adapter/claude/skill-scanner.js";import{scanDefaultSkills as K,logDefaultSkillsCheck as W}from"./default-skills/index.js";import{resolveCopilotCommand as D}from"./core/runtime/copilot-resolve.js";import{getCliVersion as V,resolveCliPath as J}from"./core/util/cli-probe.js";import{AgentInstaller as X}from"./core/installer/installer.js";import{reportInstallFailure as Y}from"./core/observability/sentry.js";const Z=8e3;function ee(){const n=D();return[{clientType:"openclaw",command:"openclaw"},{clientType:"claude",command:"claude"},{clientType:"codex",command:"codex"},{clientType:"gemini",command:"gemini"},{clientType:"qwen",command:"qwen"},{clientType:"hermes",command:"hermes"},{clientType:"reasonix",command:"reasonix"},{clientType:"codewhale",command:"codewhale"},{clientType:"opencode",command:"opencode"},{clientType:"pi",command:"pi"},{clientType:"kiro",command:"kiro-cli"},{clientType:"copilot",command:n.command},{clientType:"agy",command:"agy"}]}async function te(){return Promise.all(ee().map(async n=>{const e=await J(n.command);if(!e)return{client_type:n.clientType,command:n.command,installed:!1,path:null,version:null};const t=await V(e,n.versionArgs??["--version"]);return{client_type:n.clientType,command:n.command,installed:!0,path:e,version:t.version,error:t.error}}))}function ne(n){switch(n){case"claude":return{adapterType:"claude",command:"claude"};case"codex":return{adapterType:"codex",command:"codex",options:{sandboxMode:"danger-full-access"}};case"gemini":return{adapterType:"acp",command:"gemini",autoInjectArgs:{acp:!0},enableSessionBinding:!0};case"qwen":return{adapterType:"acp",command:"qwen",adapterHint:"qwen/base",autoInjectArgs:{acp:!0},enableSessionBinding:!0};case"pi":return{adapterType:"pi",command:"pi"};case"cursor":return{adapterType:"cursor",command:"agent"};case"reasonix":return{adapterType:"acp",command:"reasonix",args:["acp"],enableSessionBinding:!0};case"codewhale":return{adapterType:"codewhale",command:"codewhale",enableSessionBinding:!0};case"openhuman":return{adapterType:"openhuman",command:"openhuman-core",enableSessionBinding:!0};case"kiro":return{adapterType:"acp",command:"kiro-cli",args:["acp"],enableSessionBinding:!0};case"opencode":return{adapterType:"opencode",command:"opencode",args:["serve"],enableSessionBinding:!0};case"copilot":{const e=D();return{adapterType:"acp",command:e.command,args:[...e.prefixArgs,"--acp"],enableSessionBinding:!0}}case"agy":return{adapterType:"agy",command:"agy",enableSessionBinding:!0};default:throw new Error(`Unsupported client_type: ${n}`)}}function ae(n){const e=String(n??"").trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"")||"default";return x(A.data,`session-bindings-${e}.json`)}function oe(n){const e=String(n??"").trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"")||"default";return x(A.data,`active-events-${e}.json`)}function ie(...n){const e=[],t=new Set;for(const o of n)for(const a of o??[]){const r=String(a??"").trim(),u=r.toLowerCase();!r||t.has(u)||(t.add(u),e.push(r))}return e.length>0?e:void 0}function re(n,e){const t={claude:{maxConcurrent:1,maxQueued:5,queueTimeoutMs:0},codex:{maxConcurrent:1,maxQueued:5,queueTimeoutMs:0},cursor:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},acp:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},pi:{maxConcurrent:1,maxQueued:5,queueTimeoutMs:0},codewhale:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},openhuman:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},opencode:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},agy:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0}},o=t[n]??t.acp;return{maxConcurrent:e?.max_concurrent??o.maxConcurrent??1,maxQueued:e?.max_queued??o.maxQueued??3,queueTimeoutMs:e?.queue_timeout_ms??o.queueTimeoutMs??0,cancelableQueued:!0,cancelableRunning:!0}}function q(n){const e=B(),t=String(n.client_type??"").trim().toLowerCase(),o=ne(t),a=String(n.ws_url??"").trim(),r="get_session_usage",u="get_rate_limits",l="get_agent_global_config";if(!n.name?.trim())throw new Error("agent name is required");if(!a)throw new Error(`agent ${n.name}: ws_url is required`);if(!n.agent_id?.trim())throw new Error(`agent ${n.name}: agent_id is required`);if(!n.api_key?.trim())throw new Error(`agent ${n.name}: api_key is required`);const s=o.adapterType,d=s==="acp",f=t==="qwen",p={...o.options??{}},i=s==="codex"?{capabilities:["local_action_v1","agent_invoke"],localActions:["session_control","get_context","set_model","set_mode","set_reasoning_effort","set_sandbox_mode","exec_approve","exec_reject","file_list","create_folder","turn_interrupt","permission_approve","permission_reject","thread_compact",r,u]}:null,m=s==="claude"?{localActions:["session_control","set_mode","set_model","claude_interaction_reply","exec_approve","exec_reject","file_list","create_folder","thread_compact",r,u]}:null,_=f?{capabilities:["stream_chunk","local_action_v1"],localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",r],adapterHint:"qwen/base"}:null,h=s==="pi"?{adapterHint:"pi/base",capabilities:["local_action_v1"],localActions:["session_control","set_model","get_context","file_list","create_folder",r]}:null,g=s==="openhuman"?{adapterHint:"openhuman/base",capabilities:["local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",r]}:null,w=s==="cursor"?{adapterHint:"cursor/base",capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","set_mode","get_context","file_list","create_folder",r,u]}:null,b=s==="codewhale"?{capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",r]}:null,T=s==="opencode"?{adapterHint:"opencode/base",capabilities:["stream_chunk","local_action_v1"],localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",r]}:null,k=s==="agy"?{adapterHint:"agy/base",capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",r]}:null,E=d&&!f?{localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",r]}:null,P=t==="kiro"?{localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder","thread_compact",r,u]}:null,N=s==="codex"||s==="claude"||t==="gemini"?["session_control","set_model","set_mode"]:void 0,O=[r,u];d&&p.raw_transport===void 0&&(p.raw_transport=t==="gemini");const U=`${t}/base`,$=s==="claude"?"claude":s==="codex"?"codex":s==="pi"?"pi":t==="kiro"?"kiro":"gemini";let y;try{const j=$==="kiro"?void 0:process.cwd();y=z({mode:$,projectDir:j})??void 0,y&&y.length===0&&(y=void 0)}catch{}const M=K();if(M.length>0){const v=G([...y??[],...M]),I=v.filter(C=>C.source==="connector").map(C=>C.name);I.length>0&&c.info("manager",`[${n.name}] injecting connector skills: [${I.join(", ")}]`),y=v.length>0?v:void 0}return{name:n.name,adapterType:s,aibot:{url:a,agentId:n.agent_id,apiKey:n.api_key,clientType:t,clientVersion:e,adapterHint:o.adapterHint??_?.adapterHint??h?.adapterHint??g?.adapterHint??w?.adapterHint??T?.adapterHint??k?.adapterHint??U,capabilities:i?.capabilities??b?.capabilities??h?.capabilities??g?.capabilities??w?.capabilities??T?.capabilities??k?.capabilities??_?.capabilities??["stream_chunk","local_action_v1","connector_upgrade"],localActions:ie(i?.localActions??b?.localActions??m?.localActions??h?.localActions??g?.localActions??w?.localActions??T?.localActions??k?.localActions??_?.localActions??P?.localActions??E?.localActions??["exec_approve","exec_reject"],N,O,["connector_rollback","connector_upgrade_push",l]),skills:y},agent:{command:o.command,args:o.args,env:void 0},adapterOptions:p,acpAuthMethod:p.auth_method,acpInitialMode:p.initial_mode,acpMcpTools:p.acp_mcp_tools,promptTimeoutMs:n.prompt_timeout_ms,bindingsPath:ae(n.name),activeEventStorePath:oe(n.name),...o.enableSessionBinding||d?{enableSessionBinding:!0}:{},...o.autoInjectArgs?{autoInjectArgs:o.autoInjectArgs}:{},poolMaxSize:n.pool?.maxSize,poolIdleTimeoutMs:n.pool?.idleTimeoutMs,eventQueue:re(s,n.event_queue),logDir:A.log,providerBaseUrl:n.provider_base_url?.trim()||void 0,providerApiKey:n.provider_api_key?.trim()||void 0}}function se(){const n=process.env.GRIX_AGENT_STARTUP_WAIT_MS,e=Number(n);return Number.isFinite(e)&&e>=500?Math.floor(e):Z}class Te{instances=[];configMap=new Map;upgradeChecker=null;globalConfigStore;configDir=A.config;installer=new X;async start(e){const t=e??A.config;this.configDir=t,c.info("manager",`Loading configs from ${t}`),W(),this.globalConfigStore=new R(x(A.data,"agent-global-configs.json")),this.globalConfigStore.load();const o=Q(t).filter(l=>l.endsWith(".json")).sort();if(o.length===0)throw new Error(`No config files found in ${t}`);const a=[];let r=0;for(const l of o)try{const s=H(x(t,l),"utf-8"),d=JSON.parse(s);if(Array.isArray(d.agents)){if(d.agents.length===0){c.error("manager",`No agents array found in ${l}`),r++;continue}for(const f of d.agents)try{const p=q(f);a.push({config:p,file:l}),c.info("manager",`Loaded ${p.name} (${p.adapterType??"acp"}) from ${l}`)}catch(p){const i=typeof f?.name=="string"?f.name:"<unknown>";c.error("manager",`Invalid agent config in ${l} (name=${i}): ${p}`),r++}}else c.error("manager",`Unrecognized config format in ${l}`)}catch(s){c.error("manager",`Failed to load ${l}: ${s}`)}let u=0;if(a.length>0){const l=se();c.info("manager",`Starting ${a.length} agent(s), startup wait=${l}ms`);const s=()=>this.upgradeChecker?.triggerCheck(),d=i=>{this.instances=this.instances.filter(m=>m!==i)},f=a.map(({config:i})=>{const m=new S(i,this.globalConfigStore);return m.setUpgradeTrigger(s),this.instances.push(m),this.configMap.set(i.name,i),{config:i,instance:m,startPromise:m.start()}}),p=await Promise.all(f.map(async i=>{const m=await new Promise(_=>{let h=!1;const g=setTimeout(()=>{h||(h=!0,_({kind:"timeout"}))},l);i.startPromise.then(()=>{h||(h=!0,clearTimeout(g),_({kind:"started"}))}).catch(w=>{h||(h=!0,clearTimeout(g),_({kind:"failed",error:w}))})});return{task:i,outcome:m}}));for(const{task:i,outcome:m}of p)if(m.kind!=="started"){if(m.kind==="failed"){d(i.instance),c.error("manager",`Failed to start ${i.config.name}: ${m.error}`);continue}u++,c.warn("manager",`Startup pending for ${i.config.name}, continue retrying in background`),i.startPromise.then(()=>{c.info("manager",`Delayed start succeeded: ${i.config.name}`)}).catch(_=>{d(i.instance),c.error("manager",`Delayed start failed: ${i.config.name}: ${_}`)})}if(this.instances.length>0){const i=Math.max(0,this.instances.length-u);c.info("manager",`${i}/${a.length} agent(s) running now`)}u>0&&c.warn("manager",`${u} agent(s) still connecting in background`)}if(this.instances.length===0&&a.length>0)throw new Error("All agent configurations failed to start");if(a.length>0){const l=a[0].config;this.upgradeChecker=new L([{apiKey:l.aibot.apiKey,wsUrl:l.aibot.url}],()=>this.instances.some(s=>s.getStatus().busy)),await this.upgradeChecker.start()}}async stop(){c.info("manager","Stopping all agents..."),this.upgradeChecker?.stop(),await Promise.allSettled(this.instances.map(e=>e.stop())),await this.globalConfigStore?.flush(),this.instances=[],c.info("manager","All stopped")}getAgentsStatus(){return this.instances.map(e=>e.getStatus())}async addAgent(e){const t=q(e);if(this.instances.some(a=>a.name===t.name))throw new Error(`Agent "${t.name}" already exists`);const o=new S(t,this.globalConfigStore);o.setUpgradeTrigger(()=>this.upgradeChecker?.triggerCheck()),await o.start(),this.instances.push(o),this.configMap.set(t.name,t),this.persistAgentsConfig(),c.info("manager",`Added agent: ${t.name}`)}async removeAgent(e){const t=this.instances.findIndex(a=>a.name===e);if(t===-1)throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});const o=this.instances[t];this.instances.splice(t,1),this.configMap.delete(e),await o.stop(),this.persistAgentsConfig(),c.info("manager",`Removed agent: ${e}`)}persistAgentsConfig(){const e=x(this.configDir,"agents.json");try{const t=[];for(const[,a]of this.configMap)t.push({name:a.name,ws_url:a.aibot.url,agent_id:a.aibot.agentId,api_key:a.aibot.apiKey,client_type:a.aibot.clientType});F(e,JSON.stringify({agents:t},null,4)+`
|
|
2
|
-
`,"utf-8")}catch(t){c.error("manager",`Failed to persist agents config: ${t}`)}}async restartAgent(e){const t=this.instances.findIndex(u=>u.name===e);if(t===-1)throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});const o=this.configMap.get(e);if(!o)throw Object.assign(new Error(`Config for "${e}" not found`),{code:"NOT_FOUND"});await this.instances[t].stop();const r=new S(o,this.globalConfigStore);r.setUpgradeTrigger(()=>this.upgradeChecker?.triggerCheck()),await r.start(),this.instances[t]=r,c.info("manager",`Restarted agent: ${e}`)}async checkUpgrade(){return this.upgradeChecker?this.upgradeChecker.checkForUpdate():{available:!1}}triggerUpgrade(){this.upgradeChecker?.triggerCheck()}async probeAll(e={}){return
|
|
1
|
+
import{readFileSync as Q,readdirSync as F,writeFileSync as B}from"node:fs";import{join as x}from"node:path";import{AgentInstance as S}from"./bridge/bridge.js";import{GRIX_PATHS as A,log as c}from"./core/log/index.js";import{resolveClientVersion as L}from"./core/util/client-version.js";import{UpgradeChecker as R}from"./core/upgrade/upgrade-checker.js";import{AgentGlobalConfigStore as z}from"./core/persistence/agent-global-config-store.js";import{scanSkills as G,dedupeSkills as K}from"./adapter/claude/skill-scanner.js";import{scanDefaultSkills as W,logDefaultSkillsCheck as V,cleanupProjectedSkills as D}from"./default-skills/index.js";import{resolveCopilotCommand as q}from"./core/runtime/copilot-resolve.js";import{getCliVersion as J,resolveCliPath as X}from"./core/util/cli-probe.js";import{AgentInstaller as Y}from"./core/installer/installer.js";import{reportInstallFailure as Z}from"./core/observability/sentry.js";const ee=8e3;function te(){const n=q();return[{clientType:"openclaw",command:"openclaw"},{clientType:"claude",command:"claude"},{clientType:"codex",command:"codex"},{clientType:"gemini",command:"gemini"},{clientType:"qwen",command:"qwen"},{clientType:"hermes",command:"hermes"},{clientType:"reasonix",command:"reasonix"},{clientType:"codewhale",command:"codewhale"},{clientType:"opencode",command:"opencode"},{clientType:"pi",command:"pi"},{clientType:"kiro",command:"kiro-cli"},{clientType:"copilot",command:n.command},{clientType:"agy",command:"agy"}]}async function ne(){return Promise.all(te().map(async n=>{const e=await X(n.command);if(!e)return{client_type:n.clientType,command:n.command,installed:!1,path:null,version:null};const t=await J(e,n.versionArgs??["--version"]);return{client_type:n.clientType,command:n.command,installed:!0,path:e,version:t.version,error:t.error}}))}function ae(n){switch(n){case"claude":return{adapterType:"claude",command:"claude"};case"codex":return{adapterType:"codex",command:"codex",options:{sandboxMode:"danger-full-access"}};case"gemini":return{adapterType:"acp",command:"gemini",autoInjectArgs:{acp:!0},enableSessionBinding:!0};case"qwen":return{adapterType:"acp",command:"qwen",adapterHint:"qwen/base",autoInjectArgs:{acp:!0},enableSessionBinding:!0};case"pi":return{adapterType:"pi",command:"pi"};case"cursor":return{adapterType:"cursor",command:"agent"};case"reasonix":return{adapterType:"acp",command:"reasonix",args:["acp"],enableSessionBinding:!0};case"codewhale":return{adapterType:"codewhale",command:"codewhale",enableSessionBinding:!0};case"openhuman":return{adapterType:"openhuman",command:"openhuman-core",enableSessionBinding:!0};case"kiro":return{adapterType:"acp",command:"kiro-cli",args:["acp"],enableSessionBinding:!0};case"opencode":return{adapterType:"opencode",command:"opencode",args:["serve"],enableSessionBinding:!0};case"copilot":{const e=q();return{adapterType:"acp",command:e.command,args:[...e.prefixArgs,"--acp"],enableSessionBinding:!0}}case"agy":return{adapterType:"agy",command:"agy",enableSessionBinding:!0};default:throw new Error(`Unsupported client_type: ${n}`)}}function oe(n){const e=String(n??"").trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"")||"default";return x(A.data,`session-bindings-${e}.json`)}function ie(n){const e=String(n??"").trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"")||"default";return x(A.data,`active-events-${e}.json`)}function re(...n){const e=[],t=new Set;for(const o of n)for(const a of o??[]){const r=String(a??"").trim(),u=r.toLowerCase();!r||t.has(u)||(t.add(u),e.push(r))}return e.length>0?e:void 0}function se(n,e){const t={claude:{maxConcurrent:1,maxQueued:5,queueTimeoutMs:0},codex:{maxConcurrent:1,maxQueued:5,queueTimeoutMs:0},cursor:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},acp:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},pi:{maxConcurrent:1,maxQueued:5,queueTimeoutMs:0},codewhale:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},openhuman:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},opencode:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},agy:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0}},o=t[n]??t.acp;return{maxConcurrent:e?.max_concurrent??o.maxConcurrent??1,maxQueued:e?.max_queued??o.maxQueued??3,queueTimeoutMs:e?.queue_timeout_ms??o.queueTimeoutMs??0,cancelableQueued:!0,cancelableRunning:!0}}function P(n){const e=L(),t=String(n.client_type??"").trim().toLowerCase(),o=ae(t),a=String(n.ws_url??"").trim(),r="get_session_usage",u="get_rate_limits",l="get_agent_global_config";if(!n.name?.trim())throw new Error("agent name is required");if(!a)throw new Error(`agent ${n.name}: ws_url is required`);if(!n.agent_id?.trim())throw new Error(`agent ${n.name}: agent_id is required`);if(!n.api_key?.trim())throw new Error(`agent ${n.name}: api_key is required`);const s=o.adapterType,d=s==="acp",f=t==="qwen",p={...o.options??{}},i=s==="codex"?{capabilities:["local_action_v1","agent_invoke"],localActions:["session_control","get_context","set_model","set_mode","set_reasoning_effort","set_sandbox_mode","exec_approve","exec_reject","file_list","create_folder","turn_interrupt","permission_approve","permission_reject","thread_compact",r,u]}:null,m=s==="claude"?{localActions:["session_control","set_mode","set_model","claude_interaction_reply","exec_approve","exec_reject","file_list","create_folder","thread_compact",r,u]}:null,_=f?{capabilities:["stream_chunk","local_action_v1"],localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",r],adapterHint:"qwen/base"}:null,h=s==="pi"?{adapterHint:"pi/base",capabilities:["local_action_v1"],localActions:["session_control","set_model","get_context","file_list","create_folder",r]}:null,g=s==="openhuman"?{adapterHint:"openhuman/base",capabilities:["local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",r]}:null,w=s==="cursor"?{adapterHint:"cursor/base",capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","set_mode","get_context","file_list","create_folder",r,u]}:null,b=s==="codewhale"?{capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",r]}:null,T=s==="opencode"?{adapterHint:"opencode/base",capabilities:["stream_chunk","local_action_v1"],localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",r]}:null,k=s==="agy"?{adapterHint:"agy/base",capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",r]}:null,E=d&&!f?{localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",r]}:null,N=t==="kiro"?{localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder","thread_compact",r,u]}:null,O=s==="codex"||s==="claude"||t==="gemini"?["session_control","set_model","set_mode"]:void 0,U=[r,u];d&&p.raw_transport===void 0&&(p.raw_transport=t==="gemini");const H=`${t}/base`,$=s==="claude"?"claude":s==="codex"?"codex":s==="pi"?"pi":t==="kiro"?"kiro":"gemini";let y;try{const j=$==="kiro"?void 0:process.cwd();y=G({mode:$,projectDir:j})??void 0,y&&y.length===0&&(y=void 0)}catch{}const M=W();if(M.length>0){const v=K([...y??[],...M]),I=v.filter(C=>C.source==="connector").map(C=>C.name);I.length>0&&c.info("manager",`[${n.name}] injecting connector skills: [${I.join(", ")}]`),y=v.length>0?v:void 0}return{name:n.name,adapterType:s,aibot:{url:a,agentId:n.agent_id,apiKey:n.api_key,clientType:t,clientVersion:e,adapterHint:o.adapterHint??_?.adapterHint??h?.adapterHint??g?.adapterHint??w?.adapterHint??T?.adapterHint??k?.adapterHint??H,capabilities:i?.capabilities??b?.capabilities??h?.capabilities??g?.capabilities??w?.capabilities??T?.capabilities??k?.capabilities??_?.capabilities??["stream_chunk","local_action_v1","connector_upgrade"],localActions:re(i?.localActions??b?.localActions??m?.localActions??h?.localActions??g?.localActions??w?.localActions??T?.localActions??k?.localActions??_?.localActions??N?.localActions??E?.localActions??["exec_approve","exec_reject"],O,U,["connector_rollback","connector_upgrade_push",l]),skills:y},agent:{command:o.command,args:o.args,env:void 0},adapterOptions:p,acpAuthMethod:p.auth_method,acpInitialMode:p.initial_mode,acpMcpTools:p.acp_mcp_tools,promptTimeoutMs:n.prompt_timeout_ms,bindingsPath:oe(n.name),activeEventStorePath:ie(n.name),...o.enableSessionBinding||d?{enableSessionBinding:!0}:{},...o.autoInjectArgs?{autoInjectArgs:o.autoInjectArgs}:{},poolMaxSize:n.pool?.maxSize,poolIdleTimeoutMs:n.pool?.idleTimeoutMs,eventQueue:se(s,n.event_queue),logDir:A.log,providerBaseUrl:n.provider_base_url?.trim()||void 0,providerApiKey:n.provider_api_key?.trim()||void 0}}function ce(){const n=process.env.GRIX_AGENT_STARTUP_WAIT_MS,e=Number(n);return Number.isFinite(e)&&e>=500?Math.floor(e):ee}class ke{instances=[];configMap=new Map;upgradeChecker=null;globalConfigStore;configDir=A.config;installer=new Y;async start(e){const t=e??A.config;this.configDir=t,c.info("manager",`Loading configs from ${t}`),V(),D(),this.globalConfigStore=new z(x(A.data,"agent-global-configs.json")),this.globalConfigStore.load();const o=F(t).filter(l=>l.endsWith(".json")).sort();if(o.length===0)throw new Error(`No config files found in ${t}`);const a=[];let r=0;for(const l of o)try{const s=Q(x(t,l),"utf-8"),d=JSON.parse(s);if(Array.isArray(d.agents)){if(d.agents.length===0){c.error("manager",`No agents array found in ${l}`),r++;continue}for(const f of d.agents)try{const p=P(f);a.push({config:p,file:l}),c.info("manager",`Loaded ${p.name} (${p.adapterType??"acp"}) from ${l}`)}catch(p){const i=typeof f?.name=="string"?f.name:"<unknown>";c.error("manager",`Invalid agent config in ${l} (name=${i}): ${p}`),r++}}else c.error("manager",`Unrecognized config format in ${l}`)}catch(s){c.error("manager",`Failed to load ${l}: ${s}`)}let u=0;if(a.length>0){const l=ce();c.info("manager",`Starting ${a.length} agent(s), startup wait=${l}ms`);const s=()=>this.upgradeChecker?.triggerCheck(),d=i=>{this.instances=this.instances.filter(m=>m!==i)},f=a.map(({config:i})=>{const m=new S(i,this.globalConfigStore);return m.setUpgradeTrigger(s),this.instances.push(m),this.configMap.set(i.name,i),{config:i,instance:m,startPromise:m.start()}}),p=await Promise.all(f.map(async i=>{const m=await new Promise(_=>{let h=!1;const g=setTimeout(()=>{h||(h=!0,_({kind:"timeout"}))},l);i.startPromise.then(()=>{h||(h=!0,clearTimeout(g),_({kind:"started"}))}).catch(w=>{h||(h=!0,clearTimeout(g),_({kind:"failed",error:w}))})});return{task:i,outcome:m}}));for(const{task:i,outcome:m}of p)if(m.kind!=="started"){if(m.kind==="failed"){d(i.instance),c.error("manager",`Failed to start ${i.config.name}: ${m.error}`);continue}u++,c.warn("manager",`Startup pending for ${i.config.name}, continue retrying in background`),i.startPromise.then(()=>{c.info("manager",`Delayed start succeeded: ${i.config.name}`)}).catch(_=>{d(i.instance),c.error("manager",`Delayed start failed: ${i.config.name}: ${_}`)})}if(this.instances.length>0){const i=Math.max(0,this.instances.length-u);c.info("manager",`${i}/${a.length} agent(s) running now`)}u>0&&c.warn("manager",`${u} agent(s) still connecting in background`)}if(this.instances.length===0&&a.length>0)throw new Error("All agent configurations failed to start");if(a.length>0){const l=a[0].config;this.upgradeChecker=new R([{apiKey:l.aibot.apiKey,wsUrl:l.aibot.url}],()=>this.instances.some(s=>s.getStatus().busy)),await this.upgradeChecker.start()}}async stop(){c.info("manager","Stopping all agents..."),this.upgradeChecker?.stop(),await Promise.allSettled(this.instances.map(e=>e.stop())),await this.globalConfigStore?.flush(),this.instances=[],D(),c.info("manager","All stopped")}getAgentsStatus(){return this.instances.map(e=>e.getStatus())}async addAgent(e){const t=P(e);if(this.instances.some(a=>a.name===t.name))throw new Error(`Agent "${t.name}" already exists`);const o=new S(t,this.globalConfigStore);o.setUpgradeTrigger(()=>this.upgradeChecker?.triggerCheck()),await o.start(),this.instances.push(o),this.configMap.set(t.name,t),this.persistAgentsConfig(),c.info("manager",`Added agent: ${t.name}`)}async removeAgent(e){const t=this.instances.findIndex(a=>a.name===e);if(t===-1)throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});const o=this.instances[t];this.instances.splice(t,1),this.configMap.delete(e),await o.stop(),this.persistAgentsConfig(),c.info("manager",`Removed agent: ${e}`)}persistAgentsConfig(){const e=x(this.configDir,"agents.json");try{const t=[];for(const[,a]of this.configMap)t.push({name:a.name,ws_url:a.aibot.url,agent_id:a.aibot.agentId,api_key:a.aibot.apiKey,client_type:a.aibot.clientType});B(e,JSON.stringify({agents:t},null,4)+`
|
|
2
|
+
`,"utf-8")}catch(t){c.error("manager",`Failed to persist agents config: ${t}`)}}async restartAgent(e){const t=this.instances.findIndex(u=>u.name===e);if(t===-1)throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});const o=this.configMap.get(e);if(!o)throw Object.assign(new Error(`Config for "${e}" not found`),{code:"NOT_FOUND"});await this.instances[t].stop();const r=new S(o,this.globalConfigStore);r.setUpgradeTrigger(()=>this.upgradeChecker?.triggerCheck()),await r.start(),this.instances[t]=r,c.info("manager",`Restarted agent: ${e}`)}async checkUpgrade(){return this.upgradeChecker?this.upgradeChecker.checkForUpdate():{available:!1}}triggerUpgrade(){this.upgradeChecker?.triggerCheck()}async probeAll(e={}){return le(this.instances,e)}async probeOne(e,t={}){return de(this.instances,e,t)}listInstallable(){return this.installer.listInstallable()}async installAgent(e){const t=await this.installer.install(e);return Z(t),t}getInstallProgress(e){return this.installer.getProgress(e)??null}}async function le(n,e){const t=e.concurrency??4,o=Date.now(),a=new Array(n.length);await new Promise(d=>{let f=0,p=0;const i=n.length;if(i===0){d();return}function m(g){const w=n[g];w.probe(e).then(b=>{a[g]=b,_()},b=>{a[g]={agent_name:w.name,client_type:"unknown",adapter_type:"acp",ok:!1,status:"error",probed_at:Date.now(),duration_ms:0,cached:!1,cli:{command:"",installed:!1,path:null,version:null,error:{code:"internal",message:b?.message??String(b)}},conversation:{attempted:!1,ok:!1,latency_ms:null},config:{model:null,base_url:null,source:{model:"unknown",base_url:"unknown"}},process:{started:!1,alive:!1,busy:!1}},_()})}function _(){p++,f<i?m(f++):p===i&&d()}const h=Math.min(t,i);for(let g=0;g<h;g++)m(f++)});const r=a.filter(d=>d.status==="healthy").length,u=a.filter(d=>d.status==="degraded").length,l=a.filter(d=>d.status==="unavailable").length,s=await ne();return{ok:r===a.length&&a.length>0,total:a.length,healthy:r,degraded:u,unavailable:l,installed_clients:s,agents:a,probed_at:o,duration_ms:Date.now()-o}}async function de(n,e,t){const o=n.find(a=>a.name===e);if(!o)throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});return o.probe(t)}export{ke as Manager,ne as probeInstalledClientCommands,le as probeInstances,de as probeOneInstance};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import*as
|
|
1
|
+
import*as n from"node:net";const i={bind:"127.0.0.1",port:0,endpoint:"/mcp",sessionTimeoutMs:18e5,invokeTimeoutMs:3e4};function s(u){const e={bind:u?.bind??i.bind,port:u?.port??i.port,endpoint:u?.endpoint??i.endpoint,sessionTimeoutMs:u?.sessionTimeoutMs??i.sessionTimeoutMs,invokeTimeoutMs:u?.invokeTimeoutMs??i.invokeTimeoutMs,allowedOrigins:u?.allowedOrigins,allowedHosts:u?.allowedHosts};return t(e.bind),e.port!==0&&o(e.port),r(e.sessionTimeoutMs),e}function t(u){if(!u||!n.isIPv4(u)&&!n.isIPv6(u))throw new Error(`\u914D\u7F6E\u6821\u9A8C\u5931\u8D25: bind \u5730\u5740 "${u}" \u4E0D\u662F\u5408\u6CD5\u7684 IPv4 \u6216 IPv6 \u5730\u5740`)}function o(u){if(!Number.isInteger(u)||u<1||u>65535)throw new Error(`\u914D\u7F6E\u6821\u9A8C\u5931\u8D25: port \u503C ${u} \u4E0D\u5728\u5408\u6CD5\u8303\u56F4 1-65535 \u5185\u6216\u4E0D\u662F\u6574\u6570`)}function r(u){if(!Number.isInteger(u)||u<1e3||u>864e5)throw new Error(`\u914D\u7F6E\u6821\u9A8C\u5931\u8D25: session_timeout_ms \u503C ${u} \u4E0D\u5728\u5408\u6CD5\u8303\u56F4 1000-86400000 \u5185`)}export{s as createDefaultGatewayConfig};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const
|
|
1
|
+
const a=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
|
-
function a(
|
|
1
|
+
function a(e){const t=new Set([`http://127.0.0.1:${e.serverPort}`,`http://localhost:${e.serverPort}`,...e.allowedOrigins]),o=new Set([`127.0.0.1:${e.serverPort}`,`localhost:${e.serverPort}`,...e.allowedHosts]);return{validateRequest(s){const r=i(s,t);if(!r.ok)return r;const n=l(s,o);return n.ok?{ok:!0}:n}}}function i(e,t){const o=e.headers.origin;return o?t.has(o)?{ok:!0}:{ok:!1,statusCode:403,message:`Origin not allowed: ${o}`}:{ok:!0}}function l(e,t){const o=e.headers.host;return o?t.has(o)?{ok:!0}:{ok:!1,statusCode:403,message:`Host not allowed: ${o}`}:{ok:!1,statusCode:403,message:"Missing Host header"}}export{a as createSecurityPolicy};
|
|
@@ -1 +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(r,e,t,n){if(!this.registry.hasTool(e))return this.errorResult(`\u672A\u77E5\u5DE5\u5177: ${e}`);const s=a(e,t);if(!s.valid)return this.errorResult(`\u53C2\u6570\u6821\u9A8C\u5931\u8D25: ${s.error}`);if(r.status!=="ready")return this.errorResult(`\u8FDE\u63A5\u4E0D\u53EF\u7528: \u5F53\u524D\u72B6\u6001\u4E3A ${r.status}`);if(p(e))return this.executeEventTool(r,e,t);const o=i(e,t);try{const u=await r.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(r){if(r==null||typeof r!="object")return this.successResult(r??null);const e=r,t=typeof e.code=="number"?e.code:0;if(t===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=${t}]: ${n}`)}successResult(r){return{content:[{type:"text",text:JSON.stringify(r)}],isError:!1}}errorResult(r){return{content:[{type:"text",text:r}],isError:!0}}async executeEventTool(r,e,t){return e==="grix_access_control"?this.executeAccessControl(r,t):d(r,e,t)}async executeAccessControl(r,e){const t=String(e.action??""),n={pair_approve:"pair_approve",pair_deny:"pair_deny",allow_sender:"sender_allow",remove_sender:"sender_remove",set_policy:"policy_set"}[t];if(!n)return this.errorResult(`\u672A\u77E5 access_control action: ${t}`);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 r.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 o,EVENT_TOOLS as s}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=[...o.filter(t=>e.has(t.name)),...s.filter(t=>r.has(t.name))],this.toolMap=new Map(this.tools.map(t=>[t.name,t]))}getTools(){return this.tools}getTool(t){return this.toolMap.get(t)}hasTool(t){return this.toolMap.has(t)}}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"}}},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(
|
|
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(r,t){const e=C[r];if(!e)return{valid:!1,error:`\u672A\u77E5\u5DE5\u5177: ${r}`};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,u]of Object.entries(t)){if(u==null)continue;const n=e.properties[i];if(!n)continue;const s=$(i,u,n);if(s)return{valid:!1,error:s}}return{valid:!0}}function $(r,t,e){switch(e.type){case"string":if(typeof t!="string")return`\u53C2\u6570 ${r} \u7C7B\u578B\u9519\u8BEF: \u671F\u671B string\uFF0C\u5B9E\u9645 ${typeof t}`;if(e.maxLength!==void 0&&t.length>e.maxLength)return`\u53C2\u6570 ${r} \u8D85\u8FC7\u6700\u5927\u957F\u5EA6 ${e.maxLength}\uFF0C\u5B9E\u9645 ${t.length}`;if(e.enum&&!e.enum.includes(t))return`\u53C2\u6570 ${r} \u503C "${t}" \u4E0D\u5728\u5141\u8BB8\u8303\u56F4 [${e.enum.join(", ")}]`;break;case"integer":if(typeof t!="number"||!Number.isInteger(t))return`\u53C2\u6570 ${r} \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 ${r} \u503C ${t} \u5C0F\u4E8E\u6700\u5C0F\u503C ${e.minimum}`;if(e.maximum!==void 0&&t>e.maximum)return`\u53C2\u6570 ${r} \u503C ${t} \u5927\u4E8E\u6700\u5927\u503C ${e.maximum}`;if(e.enum&&!e.enum.includes(t))return`\u53C2\u6570 ${r} \u503C ${t} \u4E0D\u5728\u5141\u8BB8\u8303\u56F4 [${e.enum.join(", ")}]`;break;case"boolean":if(typeof t!="boolean")return`\u53C2\u6570 ${r} \u7C7B\u578B\u9519\u8BEF: \u671F\u671B boolean\uFF0C\u5B9E\u9645 ${typeof t}`;break;case"array":if(!Array.isArray(t))return`\u53C2\u6570 ${r} \u7C7B\u578B\u9519\u8BEF: \u671F\u671B array\uFF0C\u5B9E\u9645 ${typeof t}`;if(e.maxItems!==void 0&&t.length>e.maxItems)return`\u53C2\u6570 ${r} \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 u=t[i];if(e.items.type==="string"&&typeof u!="string")return`\u53C2\u6570 ${r}[${i}] \u7C7B\u578B\u9519\u8BEF: \u671F\u671B string\uFF0C\u5B9E\u9645 ${typeof u}`;if(e.items.type==="integer"){if(typeof u!="number"||!Number.isInteger(u))return`\u53C2\u6570 ${r}[${i}] \u7C7B\u578B\u9519\u8BEF: \u671F\u671B integer\uFF0C\u5B9E\u9645 ${typeof u}`;if(e.items.enum&&!e.items.enum.includes(u))return`\u53C2\u6570 ${r}[${i}] \u503C ${u} \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": "
|
|
3
|
+
"version": "2.0.0",
|
|
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",
|