grix-connector 2.0.10 → 2.1.1
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/cursor/cursor-adapter.js +5 -5
- package/dist/adapter/deepseek/deepseek-adapter.js +3 -3
- package/dist/adapter/opencode/opencode-adapter.js +3 -3
- package/dist/bridge/bridge.js +10 -10
- package/dist/core/files/cert-store.js +40 -1
- package/dist/core/files/file-serve.js +2 -2
- package/dist/core/files/list-handler.js +1 -1
- package/dist/default-skills/tailnet-file-share/SKILL.md +33 -2
- package/dist/grix.js +0 -0
- package/dist/service/platform-adapter.js +9 -9
- package/openclaw-plugin/index.js +20 -15
- package/package.json +1 -1
- package/scripts/install-guardian.sh +0 -0
- package/scripts/upgrade-guardian.sh +0 -0
- package/dist/adapter/claude/claude-bridge-server.js +0 -1
- package/dist/adapter/claude/claude-tools.js +0 -1
- package/dist/adapter/claude/claude-worker-client.js +0 -1
- package/dist/adapter/claude/mcp-http-launcher.js +0 -2
- package/dist/adapter/claude/result-timeout.js +0 -1
- package/dist/adapter/qwen/index.js +0 -1
- package/dist/adapter/qwen/qwen-adapter.js +0 -4
- package/dist/aibot/client.js +0 -1
- package/dist/aibot/index.js +0 -1
- package/dist/aibot/types.js +0 -0
- package/dist/core/file-ops/handler.js +0 -1
- package/dist/core/file-ops/list-files.js +0 -1
- package/dist/core/file-ops/types.js +0 -0
- package/dist/log.js +0 -3
- package/dist/main.js +0 -31
- package/dist/mcp/stream-http/config.js +0 -1
- package/dist/mcp/stream-http/connection-binding.js +0 -1
- package/dist/mcp/stream-http/event-tool-executor.js +0 -1
- package/dist/mcp/stream-http/gateway.js +0 -1
- package/dist/mcp/stream-http/index.js +0 -1
- package/dist/mcp/stream-http/security.js +0 -1
- package/dist/mcp/stream-http/session-manager.js +0 -1
- package/dist/mcp/stream-http/tool-executor.js +0 -1
- package/dist/mcp/stream-http/tool-registry.js +0 -1
- package/dist/mcp/stream-http/tool-schemas.js +0 -1
- package/dist/session/index.js +0 -1
- package/dist/session/manager.js +0 -1
- package/dist/transport/index.js +0 -1
- package/dist/transport/json-rpc.js +0 -3
|
@@ -1 +1,40 @@
|
|
|
1
|
-
import
|
|
1
|
+
import a from"node-forge";import{createHash as P}from"node:crypto";import{mkdir as C,readFile as l,writeFile as u}from"node:fs/promises";import{join as y}from"node:path";import{hostname as m}from"node:os";import{resolveRuntimePaths as D}from"../config/paths.js";const T=10,A=365,E=30,w=30;let o=null;const s=new Map;function h(){return y(D().rootDir,"certs")}function p(){return"00"+a.util.bytesToHex(a.random.getBytesSync(16))}function v(t){const e=new Date;return e.setFullYear(e.getFullYear()+t),e}function b(t){const e=new Date;return e.setDate(e.getDate()+t),e}function d(){const t=new Date;return t.setDate(t.getDate()-1),t}function x(){const t=a.pki.rsa.generateKeyPair(2048),e=a.pki.createCertificate();e.publicKey=t.publicKey,e.serialNumber=p(),e.validity.notBefore=d(),e.validity.notAfter=v(T);const i=[{name:"commonName",value:`Grix Tailnet Local CA (${m()})`},{name:"organizationName",value:"Grix Connector"}];return e.setSubject(i),e.setIssuer(i),e.setExtensions([{name:"basicConstraints",cA:!0,critical:!0},{name:"keyUsage",keyCertSign:!0,cRLSign:!0,critical:!0},{name:"subjectKeyIdentifier"}]),e.sign(t.privateKey,a.md.sha256.create()),{certPem:a.pki.certificateToPem(e),keyPem:a.pki.privateKeyToPem(t.privateKey)}}function I(t,e){const i=a.pki.certificateFromPem(e.certPem),r=a.pki.privateKeyFromPem(e.keyPem),c=a.pki.rsa.generateKeyPair(2048),n=a.pki.createCertificate();return n.publicKey=c.publicKey,n.serialNumber=p(),n.validity.notBefore=d(),n.validity.notAfter=b(A),n.setSubject([{name:"commonName",value:t}]),n.setIssuer(i.subject.attributes),n.setExtensions([{name:"basicConstraints",cA:!1},{name:"keyUsage",digitalSignature:!0,keyEncipherment:!0,critical:!0},{name:"extKeyUsage",serverAuth:!0},{name:"subjectAltName",altNames:[{type:7,ip:t}]}]),n.sign(r,a.md.sha256.create()),{key:a.pki.privateKeyToPem(c.privateKey),cert:a.pki.certificateToPem(n)}}function g(t,e){try{const i=a.pki.certificateFromPem(t),r=new Date;return r.setDate(r.getDate()+e),i.validity.notAfter>r}catch{return!1}}async function k(){if(o)return o;const t=h(),e=y(t,"tailnet-ca-cert.pem"),i=y(t,"tailnet-ca-key.pem");try{const c=await l(e,"utf8"),n=await l(i,"utf8");if(g(c,E))return o={certPem:c,keyPem:n},o}catch{}await C(t,{recursive:!0});const r=x();return await u(i,r.keyPem,{mode:384}),await u(e,r.certPem),o=r,s.clear(),r}async function N(){return(await k()).certPem}function F(t){return t.replace(/-----BEGIN CERTIFICATE-----/g,"").replace(/-----END CERTIFICATE-----/g,"").replace(/\s+/g,"")}function f(t){const e=P("sha256").update(t).digest("hex");return[e.slice(0,8),e.slice(8,12),e.slice(12,16),e.slice(16,20),e.slice(20,32)].join("-").toUpperCase()}async function R(){const t=await N(),e=F(t),i=f(t+":profile"),r=f(t+":cert"),c=`Grix Tailnet Local CA (${m()})`;return`<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>PayloadContent</key>
|
|
6
|
+
<array>
|
|
7
|
+
<dict>
|
|
8
|
+
<key>PayloadCertificateFileName</key>
|
|
9
|
+
<string>grix-tailnet-ca.crt</string>
|
|
10
|
+
<key>PayloadContent</key>
|
|
11
|
+
<data>${e}</data>
|
|
12
|
+
<key>PayloadDescription</key>
|
|
13
|
+
<string>Grix Tailnet \u672C\u673A\u81EA\u7B7E\u6839\u8BC1\u4E66</string>
|
|
14
|
+
<key>PayloadDisplayName</key>
|
|
15
|
+
<string>${c}</string>
|
|
16
|
+
<key>PayloadIdentifier</key>
|
|
17
|
+
<string>com.grix.tailnet.ca.cert</string>
|
|
18
|
+
<key>PayloadType</key>
|
|
19
|
+
<string>com.apple.security.root</string>
|
|
20
|
+
<key>PayloadUUID</key>
|
|
21
|
+
<string>${r}</string>
|
|
22
|
+
<key>PayloadVersion</key>
|
|
23
|
+
<integer>1</integer>
|
|
24
|
+
</dict>
|
|
25
|
+
</array>
|
|
26
|
+
<key>PayloadDescription</key>
|
|
27
|
+
<string>\u5B89\u88C5\u540E\u5373\u53EF\u4FE1\u4EFB\u672C\u673A Grix Tailnet \u6587\u4EF6\u670D\u52A1\u7684 HTTPS \u94FE\u63A5</string>
|
|
28
|
+
<key>PayloadDisplayName</key>
|
|
29
|
+
<string>${c}</string>
|
|
30
|
+
<key>PayloadIdentifier</key>
|
|
31
|
+
<string>com.grix.tailnet.ca</string>
|
|
32
|
+
<key>PayloadType</key>
|
|
33
|
+
<string>Configuration</string>
|
|
34
|
+
<key>PayloadUUID</key>
|
|
35
|
+
<string>${i}</string>
|
|
36
|
+
<key>PayloadVersion</key>
|
|
37
|
+
<integer>1</integer>
|
|
38
|
+
</dict>
|
|
39
|
+
</plist>
|
|
40
|
+
`}async function Y(t){const e=s.get(t);if(e&&g(e.cert,w))return e;const i=await k(),r=I(t,i);return s.set(t,r),r}function j(){o=null,s.clear()}export{N as getCaCertPem,R as getCaMobileConfig,Y as getLeafCredentials,j as resetCertCache};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{randomUUID as A}from"node:crypto";import{execFile as U}from"node:child_process";import N from"node:http";import z from"node:https";import{createReadStream as O,createWriteStream as j}from"node:fs";import{stat as f,rename as B,unlink as T,access as V,readdir as G}from"node:fs/promises";import{basename as
|
|
2
|
-
`)[0].trim();e(
|
|
1
|
+
import{randomUUID as A}from"node:crypto";import{execFile as U}from"node:child_process";import N from"node:http";import z from"node:https";import{createReadStream as O,createWriteStream as j}from"node:fs";import{stat as f,rename as B,unlink as T,access as V,readdir as G}from"node:fs/promises";import{basename as p,extname as P,isAbsolute as C,join as x,normalize as $,relative as J,resolve as M,sep as W}from"node:path";import*as X from"node:os";import{getCaCertPem as Y,getCaMobileConfig as Z,getLeafCredentials as K}from"./cert-store.js";const Q=600*1e3,L={".jpg":"image/jpeg",".jpeg":"image/jpeg",".png":"image/png",".gif":"image/gif",".webp":"image/webp",".svg":"image/svg+xml",".bmp":"image/bmp",".tiff":"image/tiff",".tif":"image/tiff",".ico":"image/x-icon",".avif":"image/avif"};function q(e){return L[P(e).toLowerCase()]}const tt={".mp4":"video/mp4",".m4v":"video/mp4",".mov":"video/quicktime",".webm":"video/webm",".ogv":"video/ogg",".mkv":"video/x-matroska",".avi":"video/x-msvideo",".3gp":"video/3gpp",".ts":"video/mp2t"},et={".mp3":"audio/mpeg",".m4a":"audio/mp4",".aac":"audio/aac",".wav":"audio/wav",".ogg":"audio/ogg",".oga":"audio/ogg",".opus":"audio/opus",".flac":"audio/flac",".weba":"audio/webm"};function nt(e){const t=P(e).toLowerCase();return L[t]??tt[t]??et[t]}function _(e){const t=e.split(".");if(t.length!==4)return!1;const i=Number(t[0]),a=Number(t[1]);return!Number.isInteger(i)||!Number.isInteger(a)?!1:i===100&&a>=64&&a<=127}function it(){return new Promise(e=>{U("tailscale",["ip","-4"],{timeout:3e3},(t,i)=>{if(t){e(void 0);return}const a=i.trim().split(`
|
|
2
|
+
`)[0].trim();e(a&&_(a)?a:void 0)})})}function at(){const e=X.networkInterfaces();for(const t of Object.values(e))if(t){for(const i of t)if(!(i.family!=="IPv4"||i.internal)&&_(i.address))return i.address}}async function ot(){const e=await it();return e!==void 0?e:at()}const m=new Map;let h=null,u="",y=0,g=null,v=null,b=0,w=null;function rt(e){for(const[t,i]of m)i.expiresAt<=e&&m.delete(t)}const st=2*1024*1024*1024;function I(e){const t=e.socket.remoteAddress??"",i=t.startsWith("::ffff:")?t.slice(7):t;return _(i)}async function ct(e,t){const i=P(t),a=p(t,i);let n=x(e,t),o=0;for(;;)try{await V(n),o++,n=x(e,`${a}(${o})${i}`)}catch{return n}}function dt(e,t,i){return new Promise((a,n)=>{const o=j(t);let r=0,s=!1;const d=()=>{s||(s=!0,o.destroy(),T(t).catch(()=>{}))};e.on("data",c=>{r+=c.length,r>st&&(d(),n(Object.assign(new Error("file too large"),{code:413})))}),o.on("error",c=>{d(),n(c)}),e.on("error",c=>{d(),n(c)}),o.on("finish",async()=>{if(!s){s=!0;try{await B(t,i),a()}catch(c){T(t).catch(()=>{}),n(c)}}}),e.pipe(o)})}async function lt(e,t){if(!I(e)){t.statusCode=403,t.end("forbidden");return}const a=new URL(e.url??"/",`http://${e.headers.host}`).searchParams.get("dir")??"";if(!C(a)||$(a)!==M(a)){t.statusCode=400,t.end("invalid dir");return}const n=e.headers["x-filename"]??"",o=Array.isArray(n)?n[0]:n;let r;try{r=decodeURIComponent(o)}catch{r=o}if(!r||r.includes("/")||r.includes("\\")||r==="."||r===".."){t.statusCode=400,t.end("invalid filename");return}try{if(!(await f(a)).isDirectory()){t.statusCode=400,t.end("dir not found");return}}catch{t.statusCode=400,t.end("dir not found");return}const s=await ct(a,r),d=`${s}.${A()}.tmp`;try{await dt(e,d,s),t.statusCode=200,t.setHeader("Content-Type","application/json"),t.end(JSON.stringify({ok:!0,path:s,name:p(s)}))}catch(c){c.code===413?(t.statusCode=413,t.end("file too large")):(t.statusCode=500,t.end("upload failed"))}}function E(e,t,i,a,n,o){const r=nt(a);if(t.setHeader("Content-Type",r??"application/octet-stream"),t.setHeader("Content-Disposition",`${o&&r?"inline":"attachment"}; filename*=UTF-8''${encodeURIComponent(a)}`),t.setHeader("Accept-Ranges","bytes"),n===0){if(e.headers.range){t.statusCode=416,t.setHeader("Content-Range","bytes */0"),t.end();return}t.statusCode=200,t.setHeader("Content-Length","0"),t.end();return}let s=0,d=n-1;const c=e.headers.range;if(c){const l=/^bytes=(\d*)-(\d*)$/.exec(c.trim());if(!l||l[1]===""&&l[2]===""){t.statusCode=416,t.setHeader("Content-Range",`bytes */${n}`),t.end();return}if(l[1]===""){const k=Number(l[2]);s=k>=n?0:n-k}else s=Number(l[1]),d=l[2]===""?n-1:Math.min(Number(l[2]),n-1);if(s>d||s>=n){t.statusCode=416,t.setHeader("Content-Range",`bytes */${n}`),t.end();return}t.statusCode=206,t.setHeader("Content-Range",`bytes ${s}-${d}/${n}`)}else t.statusCode=200;if(t.setHeader("Content-Length",String(d-s+1)),e.method==="HEAD"){t.end();return}const S=O(i,{start:s,end:d});S.on("error",()=>{t.headersSent||(t.statusCode=500),t.end()}),S.pipe(t)}function ut(e,t){const i=String(e.url??"").split("?")[0],n=/^\/d\/([A-Za-z0-9-]+)$/.exec(i)?.[1],o=n?m.get(n):void 0;if(!o||o.expiresAt<=Date.now()){t.statusCode=404,t.end("not found");return}E(e,t,o.filePath,o.fileName,o.size,!0)}async function ft(e,t){if(!I(e)){t.statusCode=403,t.end("forbidden");return}const a=new URL(e.url??"/",`http://${e.headers.host}`).searchParams.get("path")??"";if(!C(a)||$(a)!==M(a)){t.statusCode=400,t.end("invalid path");return}let n;try{n=await f(a)}catch{t.statusCode=404,t.end("not found");return}if(!n.isFile()){t.statusCode=400,t.end("not a file");return}E(e,t,a,p(a),n.size,!1)}const H=5e4;async function pt(e){const t=[],i=[e];let a=0;for(;i.length>0&&!(t.length>=H);){const n=i.pop();let o;try{o=await G(n,{withFileTypes:!0})}catch{a++;continue}for(const r of o){if(t.length>=H)break;const s=x(n,r.name),d=J(e,s).split(W).join("/");if(!r.isSymbolicLink()){if(r.isDirectory())t.push({rel:d,is_dir:!0}),i.push(s);else if(r.isFile()){let c=0;try{c=(await f(s)).size}catch{a++;continue}t.push({rel:d,is_dir:!1,size:c,abs:s})}}}}return{entries:t,unreadable:a}}async function mt(e,t){if(!I(e)){t.statusCode=403,t.end("forbidden");return}const a=new URL(e.url??"/",`http://${e.headers.host}`).searchParams.get("path")??"";if(!C(a)||$(a)!==M(a)){t.statusCode=400,t.end("invalid path");return}let n;try{n=await f(a)}catch{t.statusCode=404,t.end("not found");return}if(!n.isDirectory()){t.statusCode=400,t.end("not a directory");return}const{entries:o,unreadable:r}=await pt(a),s=o.length>=H;t.statusCode=200,t.setHeader("Content-Type","application/json"),t.end(JSON.stringify({ok:!0,root_name:p(a),truncated:s,unreadable:r,entries:o}))}function ht(e){return/iPhone|iPad|iPod/i.test(e)||/Macintosh/i.test(e)&&/Mobile/i.test(e)}async function gt(e,t){const i=String(e.headers["user-agent"]??""),n=new URL(e.url??"/",`http://${e.headers.host}`).searchParams.get("fmt");if(n==="mobileconfig"||n!=="crt"&&ht(i)){const s=await Z();t.statusCode=200,t.setHeader("Content-Type","application/x-apple-aspen-config"),t.setHeader("Content-Disposition",'attachment; filename="grix-tailnet-ca.mobileconfig"'),t.end(s);return}const r=await Y();t.statusCode=200,t.setHeader("Content-Type","application/x-x509-ca-cert"),t.setHeader("Content-Disposition",'attachment; filename="grix-tailnet-ca.crt"'),t.end(r)}function D(e,t){t.setHeader("Access-Control-Allow-Origin","*");const i=String(e.url??"").split("?")[0];if(i==="/ping"){t.statusCode=200,t.end("ok");return}if(i==="/ca"){gt(e,t).catch(()=>{t.headersSent||(t.statusCode=500,t.end("ca unavailable"))});return}if(i==="/upload"&&e.method==="POST"){lt(e,t).catch(()=>{t.headersSent||(t.statusCode=500,t.end("internal error"))});return}if(i==="/download"){ft(e,t).catch(()=>{t.headersSent||(t.statusCode=500,t.end("internal error"))});return}if(i==="/manifest"){mt(e,t).catch(()=>{t.headersSent||(t.statusCode=500,t.end("internal error"))});return}ut(e,t)}async function wt(e){const t=N.createServer(D);await new Promise((a,n)=>{t.once("error",n),t.listen(0,e,()=>{t.removeListener("error",n),a()})});const i=t.address();h=t,u=e,y=typeof i=="object"&&i?i.port:0}async function Ct(e){const{key:t,cert:i}=await K(e),a=z.createServer({key:t,cert:i},D);await new Promise((o,r)=>{a.once("error",r),a.listen(0,e,()=>{a.removeListener("error",r),o()})});const n=a.address();v=a,b=typeof n=="object"&&n?n.port:0}async function F(){const e=h,t=v;h=null,v=null,u="",y=0,b=0,await Promise.all([e&&new Promise(i=>e.close(()=>i())),t&&new Promise(i=>t.close(()=>i()))].filter(Boolean))}async function yt(e){v&&u===e||(w||(w=Ct(e).catch(t=>{throw w=null,t})),await w,w=null)}async function R(e){h&&u===e||(h&&u!==e&&await F(),g||(g=wt(e).catch(t=>{throw g=null,t})),await g,g=null),await yt(e)}async function vt(e){const t=await f(e.filePath);if(!t.isFile())throw new Error(`path is not a file: ${e.filePath}`);const i=Date.now();rt(i),await R(e.host);const a=A(),n=p(e.filePath),o=e.ttlMs&&e.ttlMs>0?e.ttlMs:Q,r=i+o;return m.set(a,{filePath:e.filePath,fileName:n,size:t.size,expiresAt:r}),{url:`https://${u}:${b}/d/${a}`,ca_install_url:`http://${u}:${y}/ca`,file_name:n,size:t.size,expires_at:r}}async function Ht(){m.clear(),await F()}async function kt(e){return await R(e),y}function At(){return b}async function Tt(e){const t=String(e.file_path??"").trim();if(!t)throw new Error("missing file_path");if(!C(t))throw new Error("file_path must be an absolute path");const i=await ot();if(!i)throw new Error("tailnet_unavailable: no tailnet IPv4 detected; ensure Tailscale is up on this host");const a=typeof e.ttl_ms=="number"?e.ttl_ms:void 0,n=await vt({filePath:t,host:i,ttlMs:a}),o=q(n.file_name)!==void 0;return{ok:!0,markdown:o?``:`[${n.file_name}](${n.url})`,is_image:o,...n}}export{ot as detectTailnetIPv4,kt as ensureServerAndGetPort,At as getFileServerHttpsPort,vt as registerFileForServe,Tt as serveLocalFile,Ht as stopFileServer,pt as walkManifest};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{realpath as
|
|
1
|
+
import{realpath as a,stat as u}from"node:fs/promises";import{resolve as _,dirname as h}from"node:path";import{listFiles as c,listWindowsDrives as d}from"./list-files.js";import{realHomeDir as f,normalizePlatformPath as w}from"./utils.js";async function b(t,s){if(process.platform==="win32"&&!t.parent_id)return{status:"ok",result:{files:await d(),current_path:""}};if(t.parent_id==="::root"){if(process.platform==="win32")return{status:"ok",result:{files:await d(),current_path:""}};t={...t,parent_id:"/"}}else t.parent_id==="::home"&&(t={...t,parent_id:f()});const e=s.fallbackDir??f(),r=t.parent_id?w(t.parent_id):null;if(process.platform!=="win32"&&r&&p(r))return l(e,t,"path_not_found",`Directory not found: ${t.parent_id}`);const o=r?_(r):e;let i;try{i=await a(o)}catch{return l(e,t,"path_not_found",`Directory not found: ${o}`)}try{(await u(i)).isDirectory()||(i=h(i))}catch{return l(e,t,"path_not_accessible",`Cannot access path: ${o}`)}try{return{status:"ok",result:{files:await c(i,{showHidden:t.show_hidden,allowedExtensions:t.allowed_extensions}),current_path:i}}}catch(n){return{status:"failed",error_code:"list_failed",error_msg:n instanceof Error?n.message:String(n)}}}async function l(t,s,e,r){try{const o=await a(t);return{status:"failed",result:{files:await c(o,{showHidden:s.show_hidden,allowedExtensions:s.allowed_extensions}),current_path:o},error_code:e,error_msg:r}}catch{return{status:"failed",error_code:e,error_msg:r}}}function p(t){return/^[A-Za-z]:[/\\]/.test(t)||t.startsWith("\\\\")}export{b as handleFileListAction};
|
|
@@ -25,10 +25,41 @@ trigger: 当用户要求查看、发送、分享、下载、导出本机上的
|
|
|
25
25
|
|
|
26
26
|
发给用户的安装引导(按其设备选其一):
|
|
27
27
|
|
|
28
|
-
- **iPhone / iPad
|
|
28
|
+
- **iPhone / iPad**:用 Safari 打开 `ca_install_url`,会直接弹出"此网站正尝试下载一个描述文件" → 允许 →
|
|
29
|
+
到「设置」顶部出现"已下载描述文件",点进去安装 →
|
|
29
30
|
再到「设置 → 通用 → 关于本机 → 证书信任设置」,把该证书开关打开(开启完全信任)。这一步必须做,否则系统仍不信任。
|
|
31
|
+
(iOS 必须用 Safari 打开,其他浏览器不会触发描述文件安装。服务端已按设备自动返回 .mobileconfig,无需手动加参数。)
|
|
30
32
|
- **Mac**:点链接下载 `.crt` → 双击用「钥匙串访问」打开 → 找到该证书 → 双击 →「信任」展开 →「使用此证书时」选"始终信任"。
|
|
31
|
-
- **Android
|
|
33
|
+
- **Android**:点 `ca_install_url` 下载 `.crt` →「设置 → 安全 → 加密与凭据 → 安装证书 → CA 证书」选择刚下载的文件安装
|
|
34
|
+
(新版安卓不支持浏览器内一键安装,必须走系统设置这一步)。
|
|
32
35
|
- **Windows**:双击 `.crt` →「安装证书」→ 选存储位置 →「将所有证书放入下列存储」→ 选"受信任的根证书颁发机构"。
|
|
33
36
|
|
|
34
37
|
如果 grix_file_link 调用失败(如未连接 Tailscale),告诉用户文件的本地路径,让他们自行获取。
|
|
38
|
+
|
|
39
|
+
## 适用范围(重要):这套证书只管 grix_file_link 自己的服务
|
|
40
|
+
|
|
41
|
+
上面这张内置 CA 和它签发的证书,**只让 grix_file_link 起的下载服务(本机 tailnet 地址)被设备信任**。它不负责、也解决不了用户自己另起的其它服务。
|
|
42
|
+
|
|
43
|
+
当用户在 tailnet 上**自行启动了别的 HTTPS 服务**(自建网站 / API / 媒体服务等),用浏览器打开报证书错误时,要分清这是另一回事:
|
|
44
|
+
|
|
45
|
+
- 设备只信任「它亲自装过的那张 CA 签出来的证书」。自建服务用的是它自己的 CA(如 mkcert),设备没装过,所以报 `不受信任 / 冒充 / NET::ERR_CERT_AUTHORITY_INVALID`——这跟有没有装 Grix 的 CA 无关。
|
|
46
|
+
- 想让自建服务也被信任,只有两条路:
|
|
47
|
+
1. 让该服务改用一张设备已信任的 CA 来签证书,并且叶子证书有效期 **≤ 398 天**(否则即使信任了 CA,仍会报 `NET::ERR_CERT_VALIDITY_TOO_LONG`);
|
|
48
|
+
2. 或者干脆别让用户开自己的 HTTPS,把要分享的文件改走 grix_file_link,由本机内置服务发出来,自动被信任、有效期也合规。
|
|
49
|
+
- 注意:Grix 内置 CA 的**私钥只留在跑连接器的这台机器上、不对外发**(`ca_install_url` 只给公钥证书,不给私钥)。因此只有**与连接器同一台机器**上的服务能借这张 CA 来签证书;其它机器上的服务必须自带 CA,并在每台设备各装一次。
|
|
50
|
+
|
|
51
|
+
简而言之:能用 grix_file_link 发的就用它发,最省事;自建服务的证书信任问题不在本技能职责内,需在那个服务侧自行解决。
|
|
52
|
+
|
|
53
|
+
## 两条信任路:Grix app 内 vs 系统浏览器(排障先分清)
|
|
54
|
+
|
|
55
|
+
同一个链接,在 **Grix app 里打开** 和在 **系统浏览器(Safari / Chrome)里打开**,走的是两套完全独立的证书信任机制,报错和解法都不同。用户反馈"打不开 / 证书报错"时,先问清他是在哪条路上看的。
|
|
56
|
+
|
|
57
|
+
- **Grix app 内**(图片预览、应用内下载等走 app 自带的网络栈):app 内置了一条信任规则——只要链接是 tailnet 地址(`100.64.0.0/10` 段)、且证书签发者名字里含 `Grix Tailnet Local CA`,就直接放行,**用户不需要在设备上安装任何证书**。所以 grix_file_link 的链接在 app 里通常开箱即用、零安装。app 内若仍打不开,多半是没走 tailnet IP、或证书签发者名字不符,而不是"没装证书"。
|
|
58
|
+
- **系统浏览器(Safari / Chrome)**:走的是手机 / 电脑的系统信任库,跟 app 那条规则毫无关系。必须按上面《HTTPS 与首次安装信任证书》把根 CA 装好(iOS 装 .mobileconfig 并开完全信任,安卓走系统设置,等等)。用户截图里出现的 `NET::ERR_CERT_AUTHORITY_INVALID`、`冒充`、`NET::ERR_CERT_VALIDITY_TOO_LONG`,几乎都是系统浏览器这条路,引导他装 CA 即可。
|
|
59
|
+
|
|
60
|
+
对**自建服务**同理,按用户要在哪看来定:
|
|
61
|
+
|
|
62
|
+
- 只需要在 **Grix app 内**展示:自建服务无需向用户分发私钥、也无需装任何证书——只要①服务挂在 tailnet IP 上、②把它那张 CA 的签发者名字带上 `Grix Tailnet Local CA`,app 就会信任。
|
|
63
|
+
- 需要在 **系统浏览器** 里打开:app 那条规则不起作用,仍需在每台设备装上该服务自己的 CA(见上一节《适用范围》)。
|
|
64
|
+
|
|
65
|
+
注意:app 内这条是**按签发者名字字符串匹配**,不是按某把 CA 的公钥指纹绑定。它仅作为私有 tailnet 内的便利取舍,**不是强安全边界**——同一 tailnet 内任何人只要把 CA 命名成相同前缀就会被 app 信任。不要据此把它当作可对抗攻击者的信任根。
|
package/dist/grix.js
CHANGED
|
File without changes
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import{mkdir as P,readdir as b,readFile as k,rm as g,stat as E,writeFile as S}from"node:fs/promises";import p from"node:os";import d from"node:path";import{runCommand as u,spawnDetached as W,killProcessesByCommandLine as L,isWindowsElevated as D}from"./process-control.js";import{getServicePrefix as x,parseConfigDirFromPlistXML as
|
|
1
|
+
import{mkdir as P,readdir as b,readFile as k,rm as g,stat as E,writeFile as S}from"node:fs/promises";import p from"node:os";import d from"node:path";import{runCommand as u,spawnDetached as W,killProcessesByCommandLine as L,isWindowsElevated as D}from"./process-control.js";import{getServicePrefix as x,parseConfigDirFromPlistXML as j,parseConfigDirFromSystemdUnit as R,resolveLinuxUserUnitPath as M,resolveMacOSLaunchAgentPath as O}from"./service-paths.js";function h(t){return String(t??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function B(t){return`'${String(t??"").replace(/'/g,"'\\''")}'`}function T(t){return[t.nodePath,t.cliPath,...t.configDir?["--config-dir",t.configDir]:[]]}function U(t){const r=T(t);return`<?xml version="1.0" encoding="UTF-8"?>
|
|
2
2
|
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
3
|
<plist version="1.0">
|
|
4
4
|
<dict>
|
|
5
5
|
<key>Label</key>
|
|
6
|
-
<string>${
|
|
6
|
+
<string>${h(t.serviceID)}</string>
|
|
7
7
|
<key>ProgramArguments</key>
|
|
8
8
|
<array>
|
|
9
|
-
${r.map(e=>` <string>${
|
|
9
|
+
${r.map(e=>` <string>${h(e)}</string>`).join(`
|
|
10
10
|
`)}
|
|
11
11
|
</array>
|
|
12
12
|
<key>RunAtLoad</key>
|
|
@@ -14,16 +14,16 @@ ${r.map(e=>` <string>${m(e)}</string>`).join(`
|
|
|
14
14
|
<key>KeepAlive</key>
|
|
15
15
|
<true/>
|
|
16
16
|
<key>WorkingDirectory</key>
|
|
17
|
-
<string>${
|
|
17
|
+
<string>${h(d.dirname(t.cliPath))}</string>
|
|
18
18
|
${t.environmentPath?` <key>EnvironmentVariables</key>
|
|
19
19
|
<dict>
|
|
20
20
|
<key>PATH</key>
|
|
21
|
-
<string>${
|
|
21
|
+
<string>${h(t.environmentPath)}</string>
|
|
22
22
|
</dict>
|
|
23
23
|
`:""} <key>StandardOutPath</key>
|
|
24
|
-
<string>${
|
|
24
|
+
<string>${h(t.stdoutPath)}</string>
|
|
25
25
|
<key>StandardErrorPath</key>
|
|
26
|
-
<string>${
|
|
26
|
+
<string>${h(t.stderrPath)}</string>
|
|
27
27
|
</dict>
|
|
28
28
|
</plist>
|
|
29
29
|
`}function V(t){const r=T(t).map(e=>B(e)).join(" ");return`[Unit]
|
|
@@ -41,6 +41,6 @@ StandardError=append:${t.stderrPath}
|
|
|
41
41
|
|
|
42
42
|
[Install]
|
|
43
43
|
WantedBy=default.target
|
|
44
|
-
`}function w(t,r){const e=t.replace(/[^a-zA-Z0-9._-]/g,"_");return d.join(r,".grix",`${e}-wrapper.vbs`)}function A(t,r){const e=t.replace(/[^a-zA-Z0-9._-]/g,"_"),a=d.join(r,"AppData","Roaming","Microsoft","Windows","Start Menu","Programs","Startup");return d.join(a,`${e}.lnk`)}function f(t){return String(t??"").replace(/"/g,'""')}function Q(t,r){const e=r.replace(/\.lnk$/,"-create.vbs"),a=["Option Explicit","Dim shell, shortcut",'Set shell = CreateObject("WScript.Shell")',`Set shortcut = shell.CreateShortcut("${f(r)}")`,'shortcut.TargetPath = "wscript.exe"',`shortcut.Arguments = "//B //NoLogo \\"${f(t)}\\""`,"shortcut.WindowStyle = 7","shortcut.Save",""].join(`\r
|
|
44
|
+
`}function w(t,r){const e=t.replace(/[^a-zA-Z0-9._-]/g,"_");return d.join(r,".grix",`${e}-wrapper.vbs`)}function A(t,r){const e=t.replace(/[^a-zA-Z0-9._-]/g,"_"),a=d.join(r,"AppData","Roaming","Microsoft","Windows","Start Menu","Programs","Startup");return d.join(a,`${e}.lnk`)}function f(t){return String(t??"").replace(/"/g,'""')}function Q(t,r){const e=d.join(d.dirname(t),d.basename(r).replace(/\.lnk$/,"-create.vbs")),a=["Option Explicit","Dim shell, shortcut",'Set shell = CreateObject("WScript.Shell")',`Set shortcut = shell.CreateShortcut("${f(r)}")`,'shortcut.TargetPath = "wscript.exe"',`shortcut.Arguments = "//B //NoLogo \\"${f(t)}\\""`,"shortcut.WindowStyle = 7","shortcut.Save",""].join(`\r
|
|
45
45
|
`);return{path:e,content:a}}function _(t){const r=[`"${f(t.nodePath)}"`,`"${f(t.cliPath)}"`,...t.configDir?["--config-dir",`"${f(t.configDir)}"`]:[]].join(" ");return["Option Explicit","Dim shell, command, delayMs, rapidCount",`command = "${f(r)}"`,'Set shell = CreateObject("WScript.Shell")',"delayMs = 5000","rapidCount = 0","Do"," shell.Run command, 0, True"," rapidCount = rapidCount + 1"," If rapidCount >= 10 Then"," WScript.Sleep 300000"," rapidCount = 0"," Else"," WScript.Sleep delayMs"," End If","Loop",""].join(`\r
|
|
46
|
-
`)}function y(t){return`gui/${t??0}`}function v(t){const r=String(t?.stdout??"").trim();return String(t?.stderr??"").trim()||r||`exit=${Number(t?.exitCode??-1)}`}function F(t,r){if(Number(t?.exitCode??0)!==0)throw new Error(`${r}: ${v(t)}`)}function z(){return{platform:"darwin",kind:"launchd",async install({serviceID:t,nodePath:r,cliPath:e,configDir:a,stdoutPath:n,stderrPath:i,environmentPath:c="",homeDir:o=p.homedir()}){const s=O(t,o);return await P(d.dirname(s),{recursive:!0}),await S(s,U({serviceID:t,nodePath:r,cliPath:e,configDir:a,stdoutPath:n,stderrPath:i,environmentPath:c}),{encoding:"utf8",mode:384}),{definitionPath:s}},async start({serviceID:t,definitionPath:r,runCommand:e=u,uid:a=process.getuid?.()??0}){const n=y(a);let i=await e("launchctl",["bootstrap",n,r],{allowFailure:!0});Number(i?.exitCode??0)!==0&&(await e("launchctl",["bootout",`${n}/${t}`],{allowFailure:!0}),i=await e("launchctl",["bootstrap",n,r],{allowFailure:!0}),F(i,`launchctl bootstrap ${n}`));const c=await e("launchctl",["kickstart","-k",`${n}/${t}`],{allowFailure:!0});if(Number(c?.exitCode??0)!==0){const o=Number(i?.exitCode??0)===0?"":`, bootstrap=${v(i)}`;throw new Error(`launchctl start failed for ${n}/${t}: ${v(c)}${o}`)}},async stop({serviceID:t,runCommand:r=u,uid:e=process.getuid?.()??0}){const a=y(e);await r("launchctl",["bootout",`${a}/${t}`],{allowFailure:!0})},async restart({serviceID:t,definitionPath:r,runCommand:e=u,uid:a=process.getuid?.()??0}){const n=y(a);await e("launchctl",["bootout",`${n}/${t}`],{allowFailure:!0});for(let o=0;o<20&&(await e("launchctl",["print",`${n}/${t}`],{allowFailure:!0})).exitCode===0;o+=1)await new Promise(l=>setTimeout(l,250));try{await E(r)}catch{throw new Error(`launchd plist missing: ${r}`)}const i=await e("launchctl",["bootstrap",n,r],{allowFailure:!0});F(i,`launchctl bootstrap ${n}`);const c=await e("launchctl",["kickstart","-k",`${n}/${t}`],{allowFailure:!0});F(c,`launchctl kickstart ${n}/${t}`)},async uninstall({serviceID:t,definitionPath:r,runCommand:e=u,uid:a=process.getuid?.()??0}){const n=y(a);await e("launchctl",["bootout",`${n}/${t}`],{allowFailure:!0}),await g(r,{force:!0})},async discoverServices({homeDir:t=p.homedir()}={}){const r=d.join(t,"Library","LaunchAgents"),e=await b(r).catch(()=>[]),a=x("darwin"),n=[];for(const i of e){if(!i.startsWith(a)||!i.endsWith(".plist"))continue;const c=i.slice(0,-6),o=d.join(r,i);let s=null;try{const l=await k(o,"utf8");s=
|
|
46
|
+
`)}function y(t){return`gui/${t??0}`}function v(t){const r=String(t?.stdout??"").trim();return String(t?.stderr??"").trim()||r||`exit=${Number(t?.exitCode??-1)}`}function F(t,r){if(Number(t?.exitCode??0)!==0)throw new Error(`${r}: ${v(t)}`)}function z(){return{platform:"darwin",kind:"launchd",async install({serviceID:t,nodePath:r,cliPath:e,configDir:a,stdoutPath:n,stderrPath:i,environmentPath:c="",homeDir:o=p.homedir()}){const s=O(t,o);return await P(d.dirname(s),{recursive:!0}),await S(s,U({serviceID:t,nodePath:r,cliPath:e,configDir:a,stdoutPath:n,stderrPath:i,environmentPath:c}),{encoding:"utf8",mode:384}),{definitionPath:s}},async start({serviceID:t,definitionPath:r,runCommand:e=u,uid:a=process.getuid?.()??0}){const n=y(a);let i=await e("launchctl",["bootstrap",n,r],{allowFailure:!0});Number(i?.exitCode??0)!==0&&(await e("launchctl",["bootout",`${n}/${t}`],{allowFailure:!0}),i=await e("launchctl",["bootstrap",n,r],{allowFailure:!0}),F(i,`launchctl bootstrap ${n}`));const c=await e("launchctl",["kickstart","-k",`${n}/${t}`],{allowFailure:!0});if(Number(c?.exitCode??0)!==0){const o=Number(i?.exitCode??0)===0?"":`, bootstrap=${v(i)}`;throw new Error(`launchctl start failed for ${n}/${t}: ${v(c)}${o}`)}},async stop({serviceID:t,runCommand:r=u,uid:e=process.getuid?.()??0}){const a=y(e);await r("launchctl",["bootout",`${a}/${t}`],{allowFailure:!0})},async restart({serviceID:t,definitionPath:r,runCommand:e=u,uid:a=process.getuid?.()??0}){const n=y(a);await e("launchctl",["bootout",`${n}/${t}`],{allowFailure:!0});for(let o=0;o<20&&(await e("launchctl",["print",`${n}/${t}`],{allowFailure:!0})).exitCode===0;o+=1)await new Promise(l=>setTimeout(l,250));try{await E(r)}catch{throw new Error(`launchd plist missing: ${r}`)}const i=await e("launchctl",["bootstrap",n,r],{allowFailure:!0});F(i,`launchctl bootstrap ${n}`);const c=await e("launchctl",["kickstart","-k",`${n}/${t}`],{allowFailure:!0});F(c,`launchctl kickstart ${n}/${t}`)},async uninstall({serviceID:t,definitionPath:r,runCommand:e=u,uid:a=process.getuid?.()??0}){const n=y(a);await e("launchctl",["bootout",`${n}/${t}`],{allowFailure:!0}),await g(r,{force:!0})},async discoverServices({homeDir:t=p.homedir()}={}){const r=d.join(t,"Library","LaunchAgents"),e=await b(r).catch(()=>[]),a=x("darwin"),n=[];for(const i of e){if(!i.startsWith(a)||!i.endsWith(".plist"))continue;const c=i.slice(0,-6),o=d.join(r,i);let s=null;try{const l=await k(o,"utf8");s=j(l)}catch{}n.push({serviceID:c,definitionPath:o,configDir:s})}return n},async isServiceLoaded({serviceID:t,runCommand:r=u,uid:e=process.getuid?.()??0}){const a=y(e),n=await r("launchctl",["print",`${a}/${t}`],{allowFailure:!0});return Number(n?.exitCode??1)===0}}}function H(){return{platform:"win32",kind:"task-scheduler",async install({serviceID:t,nodePath:r,cliPath:e,configDir:a,runCommand:n=u,homeDir:i=p.homedir()}){const c=w(t,i);await P(d.dirname(c),{recursive:!0}),await S(c,_({nodePath:r,cliPath:e,configDir:a}),"utf8");let o=!1;if(D())try{await n("schtasks",["/Create","/TN",t,"/SC","ONCE","/ST","00:00","/SD","2099/12/31","/RL","LIMITED","/F","/TR",`wscript.exe //B //NoLogo "${c}"`]),o=!0}catch{}const s=A(t,i),l=Q(c,s);return await S(l.path,l.content,"utf8"),await n("wscript.exe",[l.path,"//B","//NoLogo"]).catch(()=>{}),await g(l.path,{force:!0}),{definitionPath:o?`task:${t}`:`startup:${t}`}},async start({serviceID:t,definitionPath:r,runCommand:e=u,homeDir:a=p.homedir()}){const n=w(t,a);if(r.startsWith("task:"))try{await e("schtasks",["/Run","/TN",t]);return}catch{}W("wscript.exe",["//B","//NoLogo",n])},async stop({serviceID:t,runCommand:r=u,homeDir:e=p.homedir()}){await r("schtasks",["/End","/TN",t],{allowFailure:!0});const a=`${t}-wrapper.vbs`;await L(a,{platform:"win32"})},async restart({serviceID:t,definitionPath:r,runCommand:e=u,homeDir:a=p.homedir()}){await e("schtasks",["/End","/TN",t],{allowFailure:!0});const n=`${t}-wrapper.vbs`;await L(n,{platform:"win32"});const i=w(t,a);if(r?.startsWith("task:"))try{await e("schtasks",["/Run","/TN",t]);return}catch{}W("wscript.exe",["//B","//NoLogo",i])},async uninstall({serviceID:t,runCommand:r=u,homeDir:e=p.homedir()}){await r("schtasks",["/Delete","/TN",t,"/F"],{allowFailure:!0});const a=w(t,e);await g(a,{force:!0});const n=A(t,e);await g(n,{force:!0})},async discoverServices({homeDir:t=p.homedir(),runCommand:r=u}={}){const e=x("win32"),a=await r("schtasks",["/Query","/FO","CSV","/NH"],{allowFailure:!0}),n=[];if(Number(a?.exitCode??-1)===0){const o=String(a.stdout??"").split(/\r?\n/);for(const s of o){const l=s.match(/^"([^"]+)"/);if(!l)continue;const m=l[1];if(!m.startsWith(e))continue;let C=null;try{const $=w(m,t),N=(await k($,"utf8")).match(/--config-dir\s+""([^""]+)""/);N&&(C=N[1])}catch{}n.push({serviceID:m,definitionPath:`task:${m}`,configDir:C})}}const i=d.join(t,"AppData","Roaming","Microsoft","Windows","Start Menu","Programs","Startup"),c=await b(i).catch(()=>[]);for(const o of c){if(!o.startsWith(e)||!o.endsWith(".lnk"))continue;const s=o.slice(0,-4);if(n.some(m=>m.serviceID===s))continue;let l=null;try{const m=w(s,t),$=(await k(m,"utf8")).match(/--config-dir\s+""([^""]+)""/);$&&(l=$[1])}catch{}n.push({serviceID:s,definitionPath:`startup:${s}`,configDir:l})}return n},async isServiceLoaded({serviceID:t,runCommand:r=u}){const e=await r("schtasks",["/Query","/TN",t,"/NH"],{allowFailure:!0});return Number(e?.exitCode??1)===0}}}function Z(){return{platform:"linux",kind:"systemd-user",async install({serviceID:t,nodePath:r,cliPath:e,configDir:a,stdoutPath:n,stderrPath:i,homeDir:c=p.homedir(),runCommand:o=u}){const s=M(t,c);return await P(d.dirname(s),{recursive:!0}),await S(s,V({serviceID:t,nodePath:r,cliPath:e,configDir:a,stdoutPath:n,stderrPath:i}),{encoding:"utf8",mode:384}),await o("systemctl",["--user","daemon-reload"]),await o("systemctl",["--user","enable",`${t}.service`]),{definitionPath:s}},async start({serviceID:t,runCommand:r=u}){await r("systemctl",["--user","start",`${t}.service`])},async stop({serviceID:t,runCommand:r=u}){await r("systemctl",["--user","stop",`${t}.service`],{allowFailure:!0})},async restart({serviceID:t,runCommand:r=u}){await r("systemctl",["--user","restart",`${t}.service`])},async uninstall({serviceID:t,definitionPath:r,runCommand:e=u}){await e("systemctl",["--user","stop",`${t}.service`],{allowFailure:!0}),await e("systemctl",["--user","disable",`${t}.service`],{allowFailure:!0}),await g(r,{force:!0}),await e("systemctl",["--user","daemon-reload"])},async discoverServices({homeDir:t=p.homedir()}={}){const r=d.join(t,".config","systemd","user"),e=await b(r).catch(()=>[]),a=x("linux"),n=[];for(const i of e){if(!i.startsWith(a)||!i.endsWith(".service"))continue;const c=i.slice(0,-8),o=d.join(r,i);let s=null;try{const l=await k(o,"utf8");s=R(l)}catch{}n.push({serviceID:c,definitionPath:o,configDir:s})}return n},async isServiceLoaded({serviceID:t,runCommand:r=u}){const e=await r("systemctl",["--user","is-active",`${t}.service`],{allowFailure:!0});return String(e?.stdout??"").trim()==="active"}}}function I(t=process.platform){return t==="darwin"?z():t==="win32"?H():Z()}export{I as getPlatformServiceAdapter};
|
package/openclaw-plugin/index.js
CHANGED
|
@@ -2822,22 +2822,14 @@ async function handleFileListAction(params, context) {
|
|
|
2822
2822
|
const fallbackDir = context.fallbackDir ?? os2.homedir();
|
|
2823
2823
|
const normalizedParentId = params.parent_id ? normalizePlatformPath(params.parent_id) : null;
|
|
2824
2824
|
if (process.platform !== "win32" && normalizedParentId && isWindowsAbsolutePath(normalizedParentId)) {
|
|
2825
|
-
return {
|
|
2826
|
-
status: "failed",
|
|
2827
|
-
error_code: "path_not_found",
|
|
2828
|
-
error_msg: `Directory not found: ${params.parent_id}`
|
|
2829
|
-
};
|
|
2825
|
+
return listHomeFallback(fallbackDir, params, "path_not_found", `Directory not found: ${params.parent_id}`);
|
|
2830
2826
|
}
|
|
2831
2827
|
const targetDir = normalizedParentId ? path2.resolve(normalizedParentId) : cwd || fallbackDir;
|
|
2832
2828
|
let realTarget;
|
|
2833
2829
|
try {
|
|
2834
2830
|
realTarget = await realpath(targetDir);
|
|
2835
2831
|
} catch {
|
|
2836
|
-
return {
|
|
2837
|
-
status: "failed",
|
|
2838
|
-
error_code: "path_not_found",
|
|
2839
|
-
error_msg: `Directory not found: ${targetDir}`
|
|
2840
|
-
};
|
|
2832
|
+
return listHomeFallback(fallbackDir, params, "path_not_found", `Directory not found: ${targetDir}`);
|
|
2841
2833
|
}
|
|
2842
2834
|
if (cwd && !isWinDriveRoot(realTarget)) {
|
|
2843
2835
|
let realCwd;
|
|
@@ -2859,11 +2851,7 @@ async function handleFileListAction(params, context) {
|
|
|
2859
2851
|
realTarget = path2.dirname(realTarget);
|
|
2860
2852
|
}
|
|
2861
2853
|
} catch {
|
|
2862
|
-
return {
|
|
2863
|
-
status: "failed",
|
|
2864
|
-
error_code: "path_not_accessible",
|
|
2865
|
-
error_msg: `Cannot access path: ${targetDir}`
|
|
2866
|
-
};
|
|
2854
|
+
return listHomeFallback(fallbackDir, params, "path_not_accessible", `Cannot access path: ${targetDir}`);
|
|
2867
2855
|
}
|
|
2868
2856
|
try {
|
|
2869
2857
|
const files = await listFiles(realTarget, {
|
|
@@ -2880,6 +2868,23 @@ async function handleFileListAction(params, context) {
|
|
|
2880
2868
|
};
|
|
2881
2869
|
}
|
|
2882
2870
|
}
|
|
2871
|
+
async function listHomeFallback(homeDir, params, errorCode, errorMsg) {
|
|
2872
|
+
try {
|
|
2873
|
+
const realHome = await realpath(homeDir);
|
|
2874
|
+
const files = await listFiles(realHome, {
|
|
2875
|
+
showHidden: params.show_hidden,
|
|
2876
|
+
allowedExtensions: params.allowed_extensions
|
|
2877
|
+
});
|
|
2878
|
+
return {
|
|
2879
|
+
status: "failed",
|
|
2880
|
+
result: { files, current_path: realHome },
|
|
2881
|
+
error_code: errorCode,
|
|
2882
|
+
error_msg: errorMsg
|
|
2883
|
+
};
|
|
2884
|
+
} catch {
|
|
2885
|
+
return { status: "failed", error_code: errorCode, error_msg: errorMsg };
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2883
2888
|
function isWithinPath(target, ancestor) {
|
|
2884
2889
|
const normalizedTarget = target.endsWith(path2.sep) ? target : target + path2.sep;
|
|
2885
2890
|
const normalizedAncestor = ancestor.endsWith(path2.sep) ? ancestor : ancestor + path2.sep;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "grix-connector",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.1",
|
|
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",
|
|
File without changes
|
|
File without changes
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import c from"node:http";import{randomUUID as d}from"node:crypto";import{log as o}from"../../core/log/index.js";function l(t){t.writeHead(401,{"content-type":"application/json"}),t.end(JSON.stringify({error:"unauthorized"}))}function u(t){t.writeHead(404,{"content-type":"application/json"}),t.end(JSON.stringify({error:"not_found"}))}function h(t,e){t.writeHead(400,{"content-type":"application/json"}),t.end(JSON.stringify({error:e}))}function p(t,e={ok:!0}){t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify(e))}async function v(t){const e=[];for await(const n of t)e.push(n);const r=Buffer.concat(e).toString("utf8").trim();return r?JSON.parse(r):{}}function k(t){const e=(t.headers.authorization??"").trim();return e.toLowerCase().startsWith("bearer ")?e.slice(7).trim():""}class w{host="127.0.0.1";port=0;token;callbacks;server=null;address=null;constructor(e){this.token=d(),this.callbacks=e}getToken(){return this.token}getURL(){return this.address?`http://${this.address.address}:${this.address.port}`:""}async start(){this.server||(this.server=c.createServer(async(e,r)=>{try{await this.handleRequest(e,r)}catch(n){h(r,n instanceof Error?n.message:String(n))}}),await new Promise((e,r)=>{this.server.once("error",r),this.server.listen(this.port,this.host,()=>{this.server.off("error",r),e()})}),this.address=this.server.address(),o.info("claude-bridge",`Bridge server listening on ${this.getURL()}`))}async stop(){if(!this.server)return;const e=this.server;this.server=null,this.address=null,e.closeIdleConnections?.(),e.closeAllConnections?.(),await new Promise((r,n)=>{e.close(s=>s?n(s):r())})}async handleRequest(e,r){if(k(e)!==this.token){l(r);return}if(e.method!=="POST"){r.writeHead(405,{"content-type":"application/json"}),r.end(JSON.stringify({error:"method_not_allowed"}));return}const n=new URL(e.url,"http://localhost").pathname,s=await v(e),i=f.get(n);if(!i){u(r);return}const a=await i(this.callbacks,s);p(r,a??{ok:!0})}}const f=new Map([["/v1/worker/register",async(t,e)=>(o.info("claude-bridge",`Worker registered: ${e.worker_id} (pid=${e.pid})`),t.onRegisterWorker(e))],["/v1/worker/status",async(t,e)=>(o.info("claude-bridge",`Worker status: ${e.status}`),t.onStatusUpdate(e))],["/v1/worker/send-text",async(t,e)=>t.onSendText(e)],["/v1/worker/send-stream-chunk",async(t,e)=>t.onSendStreamChunk(e)],["/v1/worker/send-media",async(t,e)=>t.onSendMedia(e)],["/v1/worker/delete-message",async(t,e)=>t.onDeleteMessage(e)],["/v1/worker/ack-event",async(t,e)=>t.onAckEvent(e)],["/v1/worker/event-result",async(t,e)=>t.onSendEventResult(e)],["/v1/worker/event-stop-ack",async(t,e)=>t.onSendEventStopAck(e)],["/v1/worker/event-stop-result",async(t,e)=>t.onSendEventStopResult(e)],["/v1/worker/session-composing",async(t,e)=>t.onSetSessionComposing(e)],["/v1/worker/agent-invoke",async(t,e)=>t.onAgentInvoke(e)],["/v1/worker/local-action-result",async(t,e)=>t.onLocalActionResult(e)]]);export{w as ClaudeBridgeServer};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{randomUUID as x}from"node:crypto";import{CallToolRequestSchema as S,ListToolsRequestSchema as w}from"@modelcontextprotocol/sdk/types.js";import{log as f}from"../../core/log/index.js";import{toolCallToInvoke as k}from"../../core/mcp/tools.js";const E=new Set(["contact_search","session_search","message_history","message_search","group_create","group_detail_read","group_leave_self","group_member_add","group_member_remove","group_member_role_update","group_all_members_muted_update","group_member_speaking_update","group_dissolve","send_msg","delete_msg","agent_api_create","agent_category_list","agent_category_create","agent_category_update","agent_category_assign","agent_api_key_rotate"]),y=3e4,I=[{name:"reply",description:"Send a visible message back to the chat for this grix-claude event.",inputSchema:{type:"object",properties:{text:{type:"string",description:"The visible reply text to send."},chat_id:{type:"string",description:"The target chat/session id from the <channel> tag."},event_id:{type:"string",description:"The Aibot event_id from the <channel> tag."},reply_to:{type:"string",description:"Optional message_id to quote instead of the inbound trigger message."},files:{type:"array",items:{type:"string"},description:"Optional absolute local file paths. Each file is uploaded through Agent API OSS presign before sending."},final:{type:"boolean",description:"Whether this is the final reply for the event. Defaults to false \u2014 the event stays open while Claude continues working, and auto-completes after inactivity. Set true only when this is definitively the last message for the event."}},required:["chat_id","event_id"]}},{name:"complete",description:"Finish an event without sending a visible reply so the backend does not time out.",inputSchema:{type:"object",properties:{event_id:{type:"string",description:"The Aibot event_id from the <channel> tag."},status:{type:"string",enum:["responded","canceled","failed"]},code:{type:"string"},msg:{type:"string"}},required:["event_id","status"]}},{name:"delete_message",description:"Delete a previously sent message in the same grix-claude chat.",inputSchema:{type:"object",properties:{chat_id:{type:"string"},message_id:{type:"string"}},required:["chat_id","message_id"]}},{name:"status",description:"Show grix-claude runtime status, upstream access state, bridge health, and startup hints.",inputSchema:{type:"object",properties:{}}},{name:"send",description:"Send a message to a chat session proactively, without requiring an inbound event. Use for notifications or scheduled reports.",inputSchema:{type:"object",properties:{chat_id:{type:"string",description:"The target chat/session id."},text:{type:"string",description:"The message text to send."}},required:["chat_id","text"]}},{name:"access_pair",description:"Forward a sender pairing approval code to upstream access control.",inputSchema:{type:"object",properties:{code:{type:"string"}},required:["code"]}},{name:"access_deny",description:"Forward a sender pairing denial code to upstream access control.",inputSchema:{type:"object",properties:{code:{type:"string"}},required:["code"]}},{name:"allow_sender",description:"Ask upstream access control to allow a sender_id.",inputSchema:{type:"object",properties:{sender_id:{type:"string"}},required:["sender_id"]}},{name:"remove_sender",description:"Ask upstream access control to remove a sender_id.",inputSchema:{type:"object",properties:{sender_id:{type:"string"}},required:["sender_id"]}},{name:"access_policy",description:"Ask upstream access control to update the sender access policy.",inputSchema:{type:"object",properties:{policy:{type:"string",enum:["allowlist","open","disabled"]}},required:["policy"]}},{name:"grix_query",description:"Search contacts, sessions, message history, or messages by keyword in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["contact_search","session_search","message_history","message_search"]},keyword:{type:"string"},id:{type:"string"},sessionId:{type:"string"},limit:{type:"number"},offset:{type:"number"},beforeId:{type:"string"}},required:["action"]}},{name:"grix_group",description:"Manage groups in the Grix/AIBot platform: create, get details, leave, dissolve, manage members and permissions.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["create","detail","leave","add_members","remove_members","update_member_role","update_all_members_muted","update_member_speaking","dissolve"]},sessionId:{type:"string"},name:{type:"string"},memberIds:{type:"array",items:{type:"string"}},memberTypes:{type:"array",items:{type:"integer",enum:[1,2]}},memberId:{type:"string"},role:{type:"integer",enum:[1,2]},memberType:{type:"integer",description:"Member type (for update_member_role / update_member_speaking)."},allMembersMuted:{type:"boolean"},isSpeakMuted:{type:"boolean"},canSpeakWhenAllMuted:{type:"boolean",description:"Allow speaking when all muted (for update_member_speaking)."}},required:["action"]}},{name:"grix_message_send",description:"Send a message to a session in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{sessionId:{type:"string"},content:{type:"string"},msgType:{type:"number"},quotedMessageId:{type:"string"},threadId:{type:"string"}},required:["sessionId","content"]}},{name:"grix_message_unsend",description:"Recall/unsend a message in the Grix/AIBot platform.",inputSchema:{type:"object",properties:{sessionId:{type:"string"},msgId:{type:"string"}},required:["sessionId","msgId"]}},{name:"grix_admin",description:"Agent and category management in the Grix/AIBot platform: create agents, manage categories, rotate API keys.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["create_agent","list_categories","create_category","update_category","assign_category","rotate_api_key"]},agentId:{type:"string"},agentName:{type:"string"},introduction:{type:"string"},isMain:{type:"boolean"},categoryId:{type:"string"},name:{type:"string"},parentId:{type:"string"},sortOrder:{type:"number"}},required:["action"]}}];function q(n,e){n.setRequestHandler(w,async()=>({tools:I})),n.setRequestHandler(S,async i=>{const{name:r,arguments:t}=i.params,s=t??{};try{switch(r){case"reply":return await A(s,e);case"complete":return await $(s,e);case"delete_message":return await T(s,e);case"status":return j(e);case"send":return await M(s,e);case"access_pair":case"access_deny":case"allow_sender":case"remove_sender":case"access_policy":return await R(r,s,e);case"grix_query":case"grix_group":case"grix_message_send":case"grix_message_unsend":case"grix_admin":return await O(r,s,e);default:return{content:[{type:"text",text:`Unknown tool: ${r}`}],isError:!0}}}catch(a){return f.error("claude-tools",`Tool ${r} error: ${a}`),{content:[{type:"text",text:`Error: ${a instanceof Error?a.message:String(a)}`}],isError:!0}}})}async function A(n,e){const i=e.getActiveEvent();if(!i)return{content:[{type:"text",text:"No active event to reply to"}],isError:!0};const r=String(n.text??""),t=String(n.chat_id??""),s=String(n.event_id??i.eventId),a=n.reply_to,d=n.files,_=n.final===!0;if(!t||!s)return{content:[{type:"text",text:"reply requires chat_id and event_id"}],isError:!0};if(!r.trim()&&(!d||d.length===0))return{content:[{type:"text",text:"reply requires at least one of text or files"}],isError:!0};const{text:g,quotedMessageId:v}=e.resolveQuotedMessageId(a,r),b=[];let m=0;const h=`reply_${s}_${Date.now()}`;try{if(g){const p=e.splitText(g);for(let o=0;o<p.length;o++){if(!e.isEventActive(s))return c("ignored: event no longer active");m++,e.bridge.sendStreamChunk(s,t,p[o],++i.chunkSeq,!1,h)}}if(d&&d.length>0)for(const p of d){if(!e.isEventActive(s))return c("ignored: event no longer active");m++;const o=await e.uploadFile(p,t),l=`${x()}_${m}`;e.bridge.sendMedia(s,t,o.access_url,o.file_name,v,l,o.extra),f.info("claude-tools",`File sent: ${o.file_name}`)}e.bridge.sendStreamChunk(s,t,"",++i.chunkSeq,!0,h)}catch(p){if(g&&b.length===0)try{const o=`fallback_${s}_${Date.now()}`,l=e.splitText(g);for(let u=0;u<l.length;u++)e.bridge.sendStreamChunk("",t,l[u],u+1,!1,o);return e.bridge.sendStreamChunk("",t,"",l.length+1,!0,o),e.markReplySent(s),_&&e.finalizeEvent(s,"responded"),c("sent via fallback")}catch{}if(!e.isEventActive(s))return c("ignored: event no longer active");throw e.bridge.sendEventResult(s,"failed",String(p),"send_msg_failed"),p}return e.markReplySent(s),_?e.finalizeEvent(s,"responded"):(i.responded=!0,e.clearActiveEvent("completed")),c("Reply sent")}async function $(n,e){const i=e.getActiveEvent(),r=String(n.event_id??""),t=n.status??"",s=n.code,a=n.msg;if(!r||!t)return{content:[{type:"text",text:"complete requires event_id and status"}],isError:!0};const d=["responded","canceled","failed"];return d.includes(t)?e.isEventActive(r)?(e.bridge.sendEventResult(r,t,a,s),e.clearActiveEvent(t),c("Event completed")):c("ignored: event no longer active"):{content:[{type:"text",text:`status must be one of: ${d.join(", ")}`}],isError:!0}}async function T(n,e){const i=String(n.chat_id??""),r=String(n.message_id??"");if(!i||!r)return{content:[{type:"text",text:"chat_id and message_id are required"}],isError:!0};try{return await e.bridge.agentInvoke("grix_message_unsend",{sessionId:i,msgId:r},y),c(`deleted (${r})`)}catch(t){return{content:[{type:"text",text:`Delete failed: ${t}`}],isError:!0}}}function j(n){const e=n.getStatusInfo();return{content:[{type:"text",text:JSON.stringify({alive:e.alive,active_event:e.activeEvent,pending_approvals:e.pendingPermissions,pending_questions:e.pendingElicitations})}]}}async function M(n,e){const i=String(n.chat_id??""),r=String(n.text??"");if(!i||!r)return{content:[{type:"text",text:"chat_id and text are required"}],isError:!0};try{const t=e.splitText(r),s=`send_${i}_${Date.now()}`;for(let a=0;a<t.length;a++)e.bridge.sendStreamChunk("",i,t[a],a+1,!1,s);return e.bridge.sendStreamChunk("",i,"",t.length+1,!0,s),c("sent")}catch(t){return{content:[{type:"text",text:`Send failed: ${t}`}],isError:!0}}}const C={access_pair:{verb:"pair_approve",payloadKey:"code"},access_deny:{verb:"pair_deny",payloadKey:"code"},allow_sender:{verb:"sender_allow",payloadKey:"sender_id"},remove_sender:{verb:"sender_remove",payloadKey:"sender_id"},access_policy:{verb:"policy_set",payloadKey:"policy"}};async function R(n,e,i){try{const r=C[n];if(!r)throw new Error(`Unknown access control tool: ${n}`);const t={};e.code!=null&&(t.code=e.code),e.sender_id!=null&&(t.sender_id=e.sender_id),e.policy!=null&&(t.policy=e.policy);const s=await i.bridge.agentInvoke("claude_access_control",{verb:r.verb,payload:t},y);return{content:[{type:"text",text:typeof s=="string"?s:JSON.stringify(s)}]}}catch(r){return{content:[{type:"text",text:`${n} failed: ${r}`}],isError:!0}}}async function O(n,e,i){try{const r=k(n,e);if(!E.has(r.action))throw new Error(`Action not allowed: ${r.action}`);const t=await i.bridge.agentInvoke(r.action,r.params,y);if(t&&Number(t.code??0)!==0)throw new Error(String(t.msg??"invoke failed"));return{content:[{type:"text",text:t?.data!=null?typeof t.data=="string"?t.data:JSON.stringify(t.data):JSON.stringify(t)}]}}catch(r){return{content:[{type:"text",text:`${n} failed: ${r}`}],isError:!0}}}function c(n){return{content:[{type:"text",text:n}]}}export{q as registerClaudeTools};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{log as l}from"../../core/log/index.js";class c{controlURL="";token="";isConfigured(){return!!(this.controlURL&&this.token)}configure(t,e){this.controlURL=t.replace(/\/+$/,""),this.token=e.trim(),l.info("claude-worker-client",`Configured with control URL: ${this.controlURL}`)}async post(t,e,s){if(!this.isConfigured())throw new Error("worker control not configured");const i=new AbortController,o=setTimeout(()=>i.abort(),s);try{const r=await fetch(`${this.controlURL}${t}`,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${this.token}`},body:JSON.stringify(e),signal:i.signal}),n=await r.text(),a=n.trim()?JSON.parse(n):{};if(!r.ok)throw new Error(a.error||`worker control failed ${r.status}`);return a}finally{clearTimeout(o)}}isRetryableError(t){const e=t instanceof Error?t.message:String(t);return/fetch failed|network|ECONNRESET|ETIMEDOUT|EAI_AGAIN|socket hang up|aborted/i.test(e)}async postWithRetry(t,e,s,i=1){let o;for(let r=0;r<=i;r++)try{return r>0&&l.info("claude-worker-client",`Retrying ${t} attempt=${r+1}`),await this.post(t,e,s)}catch(n){if(o=n,r>=i||!this.isRetryableError(n))break;await new Promise(a=>setTimeout(a,150))}throw o instanceof Error?o:new Error(String(o))}async deliverEvent(t){return this.postWithRetry("/v1/worker/deliver-event",{payload:t},1e4,1)}async deliverStop(t){return this.postWithRetry("/v1/worker/deliver-stop",{payload:t},1e4,1)}async deliverLocalAction(t){return this.postWithRetry("/v1/worker/deliver-local-action",{payload:t},1e4,1)}async ping(){try{return await this.postWithRetry("/v1/worker/ping",{},5e3,1),!0}catch{return!1}}}export{c as ClaudeWorkerClient};
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{spawn as x,execSync as y}from"node:child_process";import{randomUUID as v}from"node:crypto";import{mkdir as S}from"node:fs/promises";import{readFileSync as C}from"node:fs";import{join as d}from"node:path";import{homedir as T,tmpdir as I}from"node:os";import{log as o}from"../../core/log/index.js";import{MCP_HTTP_CHANNEL_NAME as l}from"./protocol-contract.js";function P(e){let t=null,r=0,n=!1,i=!1;const a=v(),s=e.gatewayUrl??"http://127.0.0.1:19580/mcp";return{async start(){await F(e.command,s,e.env);const c=E(e.grix),u=[...e.args??[],"--name",`grix-mcp-${e.name}`,"--session-id",a];e.fullAuto&&u.push("--dangerously-skip-permissions"),u.push("--dangerously-load-development-channels",`server:${l}`,"--append-system-prompt",c);const f=d(I(),`grix-mcp-claude-${e.name}`);await S(f,{recursive:!0});const{expectPath:$,pidPath:g}=await M(f,e.command,u),_={...process.env,...e.env??{}};t=x("/usr/bin/expect",[$],{cwd:e.cwd,env:_,stdio:["ignore","pipe","pipe"],detached:!0}),o.info("mcp-http-launcher",`\u542F\u52A8 Claude: name=${e.name} cwd=${e.cwd} pid=${t.pid}`),r=await k(g),n=!0,o.info("mcp-http-launcher",`Claude \u5B50\u8FDB\u7A0B PID: ${r}`),t.on("exit",(m,p)=>{o.info("mcp-http-launcher",`Claude \u9000\u51FA: code=${m} signal=${p}`),n=!1,t=null,r=0,i||(o.info("mcp-http-launcher","3 \u79D2\u540E\u81EA\u52A8\u91CD\u542F..."),setTimeout(()=>{i||this.start().catch(w=>{o.error("mcp-http-launcher",`\u91CD\u542F\u5931\u8D25: ${w}`)})},3e3))}),t.stdout?.on("data",m=>{const p=m.toString().trim();p&&o.info("mcp-http-launcher",`[stdout] ${p.slice(0,300)}`)}),t.stderr?.on("data",m=>{const p=m.toString().trim();p&&o.info("mcp-http-launcher",`[stderr] ${p.slice(0,300)}`)})},async stop(){if(i=!0,n=!1,r>0)try{process.kill(r,"SIGTERM")}catch{}if(t?.pid){try{process.kill(-t.pid,"SIGTERM")}catch{}await new Promise(c=>{const u=setTimeout(()=>{if(r>0)try{process.kill(r,"SIGKILL")}catch{}if(t?.pid)try{process.kill(-t.pid,"SIGKILL")}catch{}c()},5e3);t?.once("exit",()=>{clearTimeout(u),c()})})}t=null,r=0},getStatus(){return{name:e.name,alive:n,pid:r}}}}function E(e){return["You are connected to a chat via the grix MCP server.",`On startup, immediately call grix_authorize with: agentId="${e.agentId}", apiKey="${e.apiKey}", wsUrl="${e.wsUrl}", clientType="${e.clientType}".`,"When you receive a <channel> message, you MUST respond by calling the grix_reply tool (or the grix_complete tool if no response is needed).","Never write your reply as plain text \u2014 it will NOT reach the user. Only the grix_reply tool delivers your response to the chat.","The <channel> message contains event_id and session_id \u2014 pass them to grix_reply."].join(" ")}async function F(e,t,r){const n=d(T(),".claude.json");let i=null;try{const s=C(n,"utf8");i=JSON.parse(s)?.mcpServers?.[l]??null}catch{}if(i&&String(i.type??"").trim()==="http"&&String(i.url??"").trim()===t)return;o.info("mcp-http-launcher",`\u6CE8\u518C MCP Server: ${l} -> ${t}`);const a={...process.env,...r??{}};try{y(`${e} mcp remove -s user ${l}`,{encoding:"utf8",timeout:1e4,env:a,stdio:"pipe"})}catch{}y(`${e} mcp add --scope user --transport http ${l} ${t}`,{encoding:"utf8",timeout:1e4,env:a,stdio:"pipe"})}async function M(e,t,r){const{writeFile:n}=await import("node:fs/promises"),i=d(e,"claude.pid"),a=d(e,"claude.expect"),s=["log_user 1","set timeout -1","set startup_prompt_armed 1",`set claude_command [list {${h(t)}}${r.map(c=>` {${h(c)}}`).join("")}]`,"spawn -noecho {*}$claude_command",`set pid_file [open {${h(i)}} w]`,"puts $pid_file [exp_pid -i $spawn_id]","close $pid_file","expect {"," -re {(?i)(Quick.*safety.*check|trust.*folder)} {",' if {$startup_prompt_armed} { send -- "1\\r"; after 300 }; exp_continue'," }"," -re {(?i)I am using this for local development} {",' if {$startup_prompt_armed} { send -- "1\\r"; after 300 }; exp_continue'," }"," -re {(?i)(Enter.*confirm|Press.*Enter|Hit.*Enter)} {",' if {$startup_prompt_armed} { send -- "\\r"; after 300 }; exp_continue'," }"," -re {Listening for channel} {"," set startup_prompt_armed 0"," after 1000",' send -- "Call grix_authorize now as instructed in your system prompt.\\r"'," }"," -re {bypass permissions} {"," set startup_prompt_armed 0"," after 1000",' send -- "Call grix_authorize now as instructed in your system prompt.\\r"'," }"," eof {}","}","expect eof",""];return await n(i,"","utf8"),await n(a,s.join(`
|
|
2
|
-
`),"utf8"),{expectPath:a,pidPath:i}}function h(e){return e.replace(/[\\{}$\[\]"]/g,"\\$&")}async function k(e,t=1e4){const{readFile:r}=await import("node:fs/promises"),n=Math.ceil(t/100);for(let i=0;i<n;i++){try{const a=await r(e,"utf8"),s=parseInt(String(a).trim(),10);if(Number.isFinite(s)&&s>0)return s}catch{}await new Promise(a=>setTimeout(a,100))}return 0}export{P as createMcpHttpLauncher};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
class m{defaultTimeoutMs;onTimeout;timers=new Map;constructor(t){this.defaultTimeoutMs=t.defaultTimeoutMs??9e4,this.onTimeout=t.onTimeout}arm(t,e){this.cancel(t);const s=e?.timeoutMs??this.defaultTimeoutMs,i=Date.now()+s,o=setTimeout(()=>{this.timers.delete(t),this.onTimeout(t).catch(()=>{})},s);return this.timers.set(t,o),i}cancel(t){const e=this.timers.get(t);e&&(clearTimeout(e),this.timers.delete(t))}has(t){return this.timers.has(t)}close(){for(const t of this.timers.values())clearTimeout(t);this.timers.clear()}}export{m as ResultTimeoutManager};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{QwenAdapter as e}from"./qwen-adapter.js";export{e as QwenAdapter};
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import l from"node:path";import{fileURLToPath as m}from"node:url";import{EventEmitter as h}from"node:events";import{AgentProcess as f}from"../../agent/process.js";import{AcpClient as v,AcpAuthRequiredError as g}from"../../protocol/acp-client.js";import{AgentEventType as a}from"../../types/events.js";import{QuotedMessageStream as R}from"../../core/util/quoted-message-stream.js";import{InternalApiServer as A}from"../../core/mcp/internal-api-server.js";import{EventResultsStore as b}from"../../core/persistence/event-results-store.js";import{log as r}from"../../core/log/index.js";const d=l.dirname(m(import.meta.url)),w=300*1e3,u=60*1e3,I=200;class T extends h{type="qwen";config;callbacks;agentProcess=null;acpClient=null;internalApi=null;activeRun=null;pendingApprovals=new Map;bindingStore;eventResults=null;currentAibotSessionId;stopped=!1;cwd;model;promptTimeoutMs;clientMsgSeq=0;deferredEvents=new Map;sessionBindings=new Map;constructor(e,t,s,i){super(),this.config=e,this.callbacks=t,this.bindingStore=s,this.cwd=e.cwd??process.cwd(),this.model=e.options?.model,this.promptTimeoutMs=e.options?.promptTimeoutMs??w,i&&(this.eventResults=new b(i))}async start(){const e=[...this.config.args??[],"--acp"];this.model&&e.push("--model",this.model);const t={command:this.config.command||"qwen",args:e,cwd:this.cwd,env:this.config.env},s=await this.startInternalApiAndMcp();this.agentProcess=new f;const i=await this.agentProcess.start(t);r.info("qwen-adapter","Qwen process started"),this.agentProcess.on("exit",n=>{this.stopped||(r.error("qwen-adapter",`Process exited unexpectedly (code=${n})`),this.activeRun&&this.finishRun("failed",`qwen process exited (code=${n})`),this.emit("exit",n))}),this.acpClient=new v,this.acpClient.on("event",n=>this.handleAcpEvent(n));const o=this.currentAibotSessionId?this.bindingStore.getAcpSessionId(this.currentAibotSessionId):void 0;try{await this.acpClient.connect({transport:i,initialMode:"bypass",mcpServers:s,sessionId:o,cwd:this.cwd}),r.info("qwen-adapter",`ACP session ready: ${this.acpClient.sessionId}`),this.currentAibotSessionId&&this.bindingStore.setAcpSessionId(this.currentAibotSessionId,this.acpClient.sessionId);for(const[n,c]of this.sessionBindings)this.callbacks.sendUpdateBindingCard(n,"ready",c)}catch(n){if(n instanceof g){await this.handleAuthRequired(n);return}throw n}}async stop(){this.stopped=!0,this.deferredEvents.clear(),this.activeRun&&(this.activeRun.flushTimer&&(clearTimeout(this.activeRun.flushTimer),this.activeRun.flushTimer=null),this.activeRun.timeoutTimer&&(clearTimeout(this.activeRun.timeoutTimer),this.activeRun.timeoutTimer=null),this.activeRun.firstResponseTimer&&(clearTimeout(this.activeRun.firstResponseTimer),this.activeRun.firstResponseTimer=null),this.activeRun=null),this.acpClient&&(this.acpClient.removeAllListeners(),this.acpClient=null),this.agentProcess&&(await this.agentProcess.close(),this.agentProcess=null),this.internalApi&&(await this.internalApi.stop(),this.internalApi=null)}isAlive(){return this.acpClient?.isAlive??!1}async createSession(e){return this.acpClient?.sessionId??""}async resumeSession(e,t){}async destroySession(e){}sendPrompt(e){const t=new S(e.adapterSessionId);return this.acpClient?.isAlive&&this.acpClient.send(e.text).catch(s=>{t.emitError(s instanceof Error?s:new Error(String(s)))}),t}async cancel(e){this.activeRun&&this.acpClient&&(await this.acpClient.cancel(),this.flushStream(),this.finishRun("canceled","stopped by user"))}setPermissionHandler(e){}async ping(e){return this.acpClient?.ping(e)??!1}getStatus(){return{alive:this.acpClient?.isAlive??!1,busy:this.activeRun!==null,sessions:this.acpClient?1:0}}getMcpConfig(){if(!this.internalApi)return null;const e=l.resolve(d,"../../mcp/acp-mcp-server.js");return{name:"grix-connector-tools",command:process.execPath,args:[e,"--api-url",this.internalApi.url]}}get pendingApprovalEntries(){return this.pendingApprovals}get acpSessionOptions(){return this.acpClient?.sessionOptions??null}async handleLocalAction(e){const t=e.action_type??"",s=e.params??{};if(t==="exec_approve"||t==="exec_reject"){const i=String(s.tool_call_id??""),o=t==="exec_approve";if(!i)return this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"tool_call_id_required","tool_call_id is required"),{handled:!0,kind:"approval"};const n=this.pendingApprovals.get(i);return n?(this.pendingApprovals.delete(i),this.acpClient&&this.acpClient.respondPermission(n,{behavior:o?"allow":"deny"}).catch(c=>{r.error("qwen-adapter",`Failed to respond to permission: ${c}`)}),this.callbacks.sendLocalActionResult(e.action_id,"ok"),{handled:!0,kind:"approval"}):(this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"approval_not_found",`no pending approval for tool_call_id: ${i}`),{handled:!0,kind:"approval"})}return{handled:!1,kind:""}}async startInternalApiAndMcp(){try{this.internalApi=new A,this.internalApi.setInvokeHandler(async(t,s)=>this.callbacks.agentInvoke(t,s)),await this.internalApi.start(0),r.info("qwen-adapter",`Internal API started at ${this.internalApi.url}`);const e=l.resolve(d,"../../mcp/acp-mcp-server.js");return[{name:"grix-connector-tools",command:process.execPath,args:[e,"--api-url",this.internalApi.url],env:{GRIX_CONNECTOR_INTERNAL_API:this.internalApi.url}}]}catch(e){r.error("qwen-adapter",`Failed to start MCP tools: ${e}`);return}}bindSession(e,t){return this.sessionBindings.get(e)?!1:(this.sessionBindings.set(e,t),this.bindingStore.set(e,t),this.acpClient?.sessionId&&this.bindingStore.setAcpSessionId(e,this.acpClient.sessionId),this.acpClient?.isAlive&&this.callbacks.sendUpdateBindingCard(e,"connected",t),!0)}getSessionCwd(e){return this.sessionBindings.get(e)}getSessionBindings(){return this.sessionBindings}deliverInboundEvent(e){if(this.callbacks.sendEventAck(e.event_id,e.session_id),this.eventResults?.has(e.session_id,e.event_id)){const t=this.eventResults.get(e.session_id,e.event_id);r.info("qwen-adapter",`Deduplicating event ${e.event_id} (cached: ${t.status})`),this.callbacks.sendEventResult(e.event_id,t.status,t.msg);return}if(this.activeRun){r.info("qwen-adapter",`Event ${e.event_id} rejected: busy`),this.callbacks.sendEventResult(e.event_id,"failed","agent busy");return}if(!this.acpClient?.isAlive){this.callbacks.sendEventResult(e.event_id,"failed","qwen agent not alive");return}if(e.session_id&&e.session_id!==this.currentAibotSessionId&&(this.currentAibotSessionId=e.session_id),!this.sessionBindings.has(e.session_id)){this.deferEvent(e),this.callbacks.sendStreamChunk(e.event_id,e.session_id,"Qwen needs a workspace before it can reply. Use /grix open <directory> to bind.",1,!1),this.callbacks.sendStreamChunk(e.event_id,e.session_id,"",2,!0),this.callbacks.sendEventResult(e.event_id,"responded");return}this.startRun(e,!1)}deliverStopEvent(e){this.activeRun?.eventId===e&&(this.flushStream(),this.finishRun("canceled","stopped by user"))}deferEvent(e){const t=this.deferredEvents.get(e.session_id)??[];t.some(s=>s.event.event_id===e.event_id)||(t.push({event:e,queuedAt:Date.now()}),this.deferredEvents.set(e.session_id,t),r.info("qwen-adapter",`Deferred event ${e.event_id} for session ${e.session_id} (queue: ${t.length})`))}replayDeferredEvents(e){const t=this.deferredEvents.get(e);if(!(!t||t.length===0)){this.deferredEvents.delete(e),r.info("qwen-adapter",`Replaying ${t.length} deferred events for session ${e}`);for(const{event:s}of t){if(this.activeRun){r.info("qwen-adapter",`Cannot replay ${s.event_id}: agent busy, dropping`);continue}this.startRun(s,!0)}}}startRun(e,t){this.activeRun={eventId:e.event_id,sessionId:e.session_id,threadId:e.thread_id,clientMsgId:`qwen_${++this.clientMsgSeq}_${Date.now()}`,chunkSeq:0,buffer:"",quotedStream:new R,responded:!1,silent:t,flushTimer:null,timeoutTimer:null,firstResponseTimer:null};const s=this.activeRun;s.firstResponseTimer=setTimeout(()=>{this.activeRun?.eventId===e.event_id&&!s.responded&&(r.error("qwen-adapter",`No response from agent within ${u}ms for ${e.event_id}`),this.finishRun("failed","agent not responding"))},u),s.timeoutTimer=setTimeout(()=>{this.activeRun?.eventId===e.event_id&&(r.error("qwen-adapter",`Prompt timed out for ${e.event_id}`),this.finishRun("failed","agent response timed out"))},this.promptTimeoutMs),this.callbacks.sendSessionComposing(e.session_id,!0),this.acpClient.send(e.content).catch(i=>{r.error("qwen-adapter",`Prompt failed: ${i}`),this.finishRun("failed",i instanceof Error?i.message:String(i))})}handleAcpEvent(e){if(e.type===a.PermissionRequest){this.handlePermissionRequest(e);return}const t=this.activeRun;if(t)switch(t.responded||(t.responded=!0,t.firstResponseTimer&&(clearTimeout(t.firstResponseTimer),t.firstResponseTimer=null)),e.type){case a.Text:{e.content&&this.appendToStream(t,e.content);break}case a.ToolUse:{e.toolName&&this.callbacks.sendToolUse(t.eventId,t.sessionId,e.toolName,e.toolInput??"");break}case a.ToolResult:{e.content&&this.callbacks.sendToolResult(t.eventId,t.sessionId,e.content);break}case a.Thinking:{e.content&&this.callbacks.sendThinking(t.eventId,t.sessionId,e.content);break}case a.Error:{r.error("qwen-adapter",`ACP error: ${e.error}`);break}case a.Result:{this.flushStream(),this.finishRun("responded");break}}}handlePermissionRequest(e){const t=e.permissionRequest;if(!t||!e.requestId||!this.acpClient)return;const s=t.toolCallId;this.pendingApprovals.set(s,e.requestId);const i=this.activeRun;i?this.callbacks.sendPermissionCard({eventId:i.eventId,sessionId:i.sessionId,toolCallId:s,toolName:t.toolName,toolTitle:t.toolTitle,options:t.options}):(r.info("qwen-adapter",`Permission request without active run, auto-approving: ${t.toolName}`),this.acpClient.respondPermission(e.requestId,{behavior:"allow"}),this.pendingApprovals.delete(s))}async handleAuthRequired(e){r.info("qwen-adapter",`Auth required, methods: ${e.authMethods.map(o=>o.id).join(", ")}`);const t=e.authMethods.find(o=>/oauth|browser/i.test(o.id))??e.authMethods[0];if(!t)throw e;const s=this.currentAibotSessionId??"";this.callbacks.sendAuthNotification(s,`Qwen authentication required (${t.id}). Initiating auth flow...`);for(const[o,n]of this.sessionBindings)this.callbacks.sendUpdateBindingCard(o,"failed",n);const i=await this.captureAuthUrl();i&&this.callbacks.sendAuthNotification(s,`Please open this URL to authenticate:
|
|
2
|
-
${i}
|
|
3
|
-
|
|
4
|
-
Waiting for authentication to complete...`);try{await this.acpClient.authenticate(t.id),r.info("qwen-adapter","Authentication successful"),this.callbacks.sendAuthNotification(s,"Authentication successful. Resuming..."),await this.acpClient.connect({transport:this.agentProcess.transport,initialMode:"bypass",mcpServers:this.internalApi?[{name:"grix-connector-tools",command:process.execPath,args:[l.resolve(d,"../../mcp/acp-mcp-server.js"),"--api-url",this.internalApi.url],env:{GRIX_CONNECTOR_INTERNAL_API:this.internalApi.url}}]:void 0}),r.info("qwen-adapter",`ACP session ready after auth: ${this.acpClient.sessionId}`);for(const[o,n]of this.sessionBindings)this.callbacks.sendUpdateBindingCard(o,"ready",n)}catch(o){throw r.error("qwen-adapter",`Auth retry failed: ${o}`),o}}captureAuthUrl(){return new Promise(e=>{if(!this.agentProcess){e(null);return}const t=/https?:\/\/[^\s"')\]]+/;let s=!1;const i=o=>{if(s)return;const n=o.toString().replace(/\x1b\[[0-9;]*m/g,"").match(t);n&&(s=!0,this.agentProcess.removeListener("stderr",i),e(n[0]))};this.agentProcess.on("stderr",i),setTimeout(()=>{s||(s=!0,this.agentProcess.removeListener("stderr",i),e(null))},3e4)})}appendToStream(e,t){const s=e.quotedStream.consume(t);s.deltaContent&&(e.buffer+=s.deltaContent,e.flushTimer||(e.flushTimer=setTimeout(()=>this.flushStream(),I)))}flushStream(){const e=this.activeRun;if(!e||!e.buffer)return;e.flushTimer&&(clearTimeout(e.flushTimer),e.flushTimer=null);const t=e.buffer;e.buffer="",this.callbacks.sendStreamChunk(e.eventId,e.sessionId,t,++e.chunkSeq,!1)}finishRun(e,t){const s=this.activeRun;if(!s)return;this.activeRun=null,this.callbacks.sendSessionComposing(s.sessionId,!1),s.flushTimer&&(clearTimeout(s.flushTimer),s.flushTimer=null),s.timeoutTimer&&(clearTimeout(s.timeoutTimer),s.timeoutTimer=null),s.firstResponseTimer&&(clearTimeout(s.firstResponseTimer),s.firstResponseTimer=null);const i=s.quotedStream.flush();i.deltaContent&&(s.buffer+=i.deltaContent),s.buffer&&(this.callbacks.sendStreamChunk(s.eventId,s.sessionId,s.buffer,++s.chunkSeq,!1),s.buffer=""),t&&this.callbacks.sendRunError(s.eventId,s.sessionId,t),this.callbacks.sendStreamChunk(s.eventId,s.sessionId,"",++s.chunkSeq,!0),s.silent||this.callbacks.sendEventResult(s.eventId,e,t),this.eventResults&&!s.silent&&this.eventResults.set({sessionId:s.sessionId,eventId:s.eventId,status:e,msg:t,updatedAt:Date.now()})}}class S extends h{adapterSessionId;constructor(e){super(),this.adapterSessionId=e}emitDone(e){this.emit("done",e)}emitError(e){this.emit("error",e)}async cancel(){}}export{T as QwenAdapter};
|
package/dist/aibot/client.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{EventEmitter as o}from"node:events";import c from"ws";const r="aibot-agent-api-v1",h=1;class l extends o{ws=null;seq=0;heartbeatTimer=null;heartbeatSec=30;connected=!1;config;constructor(e){super(),this.config={url:e.url,agentId:e.agentId,apiKey:e.apiKey,clientType:e.clientType,capabilities:e.capabilities??["stream_chunk","local_action_v1"],localActions:e.localActions??["exec_approve","exec_reject"]}}get isConnected(){return this.connected}async connect(){return new Promise((e,a)=>{const s=new c(this.config.url);this.ws=s;const i=setTimeout(()=>{a(new Error("Auth timeout: no auth_ack received within 15s")),s.close()},15e3);s.on("open",()=>{this.sendPacket("auth",{agent_id:this.config.agentId,api_key:this.config.apiKey,client_type:this.config.clientType,protocol_version:r,contract_version:h,capabilities:this.config.capabilities,local_actions:this.config.localActions})}),s.on("message",t=>{let n;try{n=JSON.parse(t.toString())}catch{return}this.handlePacket(n,i,e,a)}),s.on("close",(t,n)=>{this.connected=!1,this.stopHeartbeat(),clearTimeout(i),this.emit("close",t,n.toString())}),s.on("error",t=>{clearTimeout(i),this.emit("error",t),this.connected||a(t)})})}handlePacket(e,a,s,i){switch(e.cmd){case"auth_ack":{clearTimeout(a);const t=e.payload;t.code===0?(this.connected=!0,t.heartbeat_sec&&(this.heartbeatSec=t.heartbeat_sec),this.startHeartbeat(),this.emit("auth",t),s(t)):i(new Error(`Auth failed: code=${t.code} msg=${t.msg}`));break}case"ping":{this.sendPacket("pong",e.payload??{});break}case"event_msg":{this.emit("event",e.payload);break}case"local_action":{this.emit("localAction",e.payload);break}case"event_stop":{this.emit("stop",e.payload);break}case"kicked":{this.emit("kicked",e.payload),this.disconnect();break}case"error":{const t=e.payload;this.emit("error",new Error(`Server error: code=${t.code} msg=${t.msg}`));break}case"send_ack":case"send_nack":case"local_action_ack":break;default:break}}sendEventAck(e){this.sendPacket("event_ack",e)}sendStreamChunk(e){this.sendPacket("client_stream_chunk",e)}sendMsg(e){this.sendPacket("send_msg",e)}sendEventResult(e){this.sendPacket("event_result",e)}sendLocalActionResult(e){this.sendPacket("local_action_result",e)}sendEventStopAck(e){this.sendPacket("event_stop_ack",e)}sendEventStopResult(e){this.sendPacket("event_stop_result",e)}sendPing(){this.sendPacket("ping",{})}disconnect(){this.connected=!1,this.stopHeartbeat(),this.ws&&(this.ws.close(),this.ws=null)}sendPacket(e,a){if(!this.ws||this.ws.readyState!==c.OPEN)return;const s={cmd:e,seq:++this.seq,payload:a};this.ws.send(JSON.stringify(s))}startHeartbeat(){this.stopHeartbeat(),this.heartbeatTimer=setInterval(()=>{this.connected&&this.sendPing()},this.heartbeatSec*1e3)}stopHeartbeat(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null)}}export{l as AibotClient};
|
package/dist/aibot/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export*from"./types.js";import{AibotClient as o}from"./client.js";export{o as AibotClient};
|
package/dist/aibot/types.js
DELETED
|
File without changes
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{mkdir as m}from"node:fs/promises";import{join as c,basename as l}from"node:path";import{homedir as _}from"node:os";import{listFiles as u}from"./list-files.js";function d(){return _()}async function f(a,s){const o=a.params??{},t=o.parent_id?.trim(),i=o.show_hidden??!1;let e;t?e=t:e=s.resolveCwd()||d();try{return{status:"ok",result:{files:await u(e,i),current_path:e}}}catch(r){return r?.code==="ENOENT"?{status:"failed",error_code:"path_not_found",error_msg:`Directory not found: ${e}`}:r?.code==="ENOTDIR"?{status:"failed",error_code:"not_a_directory",error_msg:`Not a directory: ${e}`}:{status:"failed",error_code:"list_failed",error_msg:String(r.message||r)}}}async function p(a,s){const o=a.params??{},t=o.name?.trim(),i=o.parent_id?.trim();if(!t)return{status:"failed",error_code:"name_required",error_msg:"Folder name is required"};if(/[/\\]/.test(t))return{status:"failed",error_code:"invalid_name",error_msg:"Folder name must not contain path separators"};const e=i||s.resolveCwd()||d(),r=c(e,t);try{return await m(r),{status:"ok",result:{id:r,name:l(r),is_directory:!0}}}catch(n){return n?.code==="EEXIST"?{status:"failed",error_code:"already_exists",error_msg:`Folder already exists: ${r}`}:{status:"failed",error_code:"create_failed",error_msg:String(n.message||n)}}}export{p as handleCreateFolderAction,f as handleFileListAction};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{readdir as r,stat as m}from"node:fs/promises";import{join as l,extname as d}from"node:path";const x={pdf:"application/pdf",doc:"application/msword",docx:"application/vnd.openxmlformats-officedocument.wordprocessingml.document",xls:"application/vnd.ms-excel",xlsx:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",ppt:"application/vnd.ms-powerpoint",pptx:"application/vnd.openxmlformats-officedocument.presentationml.presentation",txt:"text/plain",md:"text/markdown",csv:"text/csv",json:"application/json",xml:"application/xml",yaml:"text/yaml",yml:"text/yaml",html:"text/html",css:"text/css",js:"text/javascript",ts:"text/typescript",zip:"application/zip",rar:"application/x-rar-compressed","7z":"application/x-7z-compressed",tar:"application/x-tar",gz:"application/gzip",jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",gif:"image/gif",webp:"image/webp",svg:"image/svg+xml",mp4:"video/mp4",mov:"video/quicktime",avi:"video/x-msvideo",mkv:"video/x-matroska",webm:"video/webm",mp3:"audio/mpeg",wav:"audio/wav",flac:"audio/flac",aac:"audio/aac"};function n(a){const p=d(a).slice(1).toLowerCase();return x[p]}async function f(a,p=!1){const c=await r(a,{withFileTypes:!0}),s=[];for(const t of c){if(!p&&t.name.startsWith("."))continue;const i=l(a,t.name),e={id:i,name:t.name,is_directory:t.isDirectory()};try{if(t.isDirectory()){const o=await m(i);e.modified_at=o.mtime.toISOString()}else{const o=await m(i);e.size=o.size,e.modified_at=o.mtime.toISOString(),e.mime_type=n(t.name)}}catch{}s.push(e)}return s.sort((t,i)=>t.is_directory!==i.is_directory?t.is_directory?-1:1:t.name.localeCompare(i.name)),s}export{f as listFiles,n as resolveMimeType};
|
|
File without changes
|
package/dist/log.js
DELETED
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import{createWriteStream as g,mkdirSync as l,existsSync as f}from"node:fs";import{join as t}from"node:path";import{homedir as m}from"node:os";const i=t(m(),".grix"),s={base:i,config:t(i,"config"),log:t(i,"log"),data:t(i,"data")};function S(){for(const o of Object.values(s))f(o)||l(o,{recursive:!0})}let a=null;function $(){const o=new Date().toISOString().slice(0,10),r=t(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,...n){const e=`${c()} [${o}] ${r}${n.length?" "+n.map(String).join(" "):""}`;console.log(e),a?.write(e+`
|
|
2
|
-
`)},error(o,r,...n){const e=`${c()} [${o}] ERROR ${r}${n.length?" "+n.map(String).join(" "):""}`;console.error(e),a?.write(e+`
|
|
3
|
-
`)}};export{s as GRIX_PATHS,S as ensureGrixDirs,$ as initLogger,u as log};
|
package/dist/main.js
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import x from"node:path";import{Manager as E}from"./manager.js";import{ensureGrixDirs as k,initLogger as O,log as a}from"./core/log/index.js";import{HealthServer as I}from"./core/runtime/index.js";import{writePidFile as R,removePidFile as l}from"./core/runtime/index.js";import{resolveRuntimePaths as g}from"./core/config/index.js";import{ServiceManager as T}from"./service/service-manager.js";import{acquireDaemonLock as $,releaseDaemonLock as p}from"./runtime/daemon-lock.js";import{writeDaemonStatus as f,removeDaemonStatus as F}from"./runtime/service-state.js";const n=process.argv.slice(2),m=[],s={};for(let e=0;e<n.length;e++)n[e].startsWith("--")&&n[e+1]&&!n[e+1].startsWith("--")?(s[n[e].slice(2)]=n[e+1],e++):n[e].startsWith("--")?s[n[e].slice(2)]="true":m.push(n[e]);if(s.help&&(console.log(`grix-connector \u2014 Unified AI Agent Bridge
|
|
3
|
-
|
|
4
|
-
Usage: grix-connector [options]
|
|
5
|
-
grix-connector service <action> [options]
|
|
6
|
-
|
|
7
|
-
Actions (service):
|
|
8
|
-
install Install and start as OS service
|
|
9
|
-
start Start the OS service
|
|
10
|
-
stop Stop the OS service
|
|
11
|
-
restart Restart the OS service
|
|
12
|
-
uninstall Uninstall the OS service
|
|
13
|
-
status Show service and daemon status
|
|
14
|
-
|
|
15
|
-
Options:
|
|
16
|
-
--config-dir <path> Config directory (default: ~/.grix/config)
|
|
17
|
-
--profile <name> Profile name for config subdirectory
|
|
18
|
-
--health-port <port> Health check port (default: 19579)
|
|
19
|
-
--help Show this help message
|
|
20
|
-
|
|
21
|
-
Platform services:
|
|
22
|
-
macOS: launchd (LaunchAgent)
|
|
23
|
-
Linux: systemd --user
|
|
24
|
-
Windows: Task Scheduler
|
|
25
|
-
|
|
26
|
-
Examples:
|
|
27
|
-
grix-connector # Run in foreground
|
|
28
|
-
grix-connector service install # Install as OS service
|
|
29
|
-
grix-connector service status # Check service status
|
|
30
|
-
grix-connector service uninstall # Remove OS service
|
|
31
|
-
`),process.exit(0)),m[0]==="service"){const e=m[1],t=["install","start","stop","restart","uninstall","status"];(!e||!t.includes(e))&&(console.error(`Usage: grix-connector service <${t.join("|")}>`),process.exit(1));const o=g(),w=s["config-dir"]??(s.profile?`${o.configDir}/${s.profile}`:void 0),D=x.resolve(process.argv[1]||`${o.rootDir}/dist/main.js`),c=new T({cliPath:D,nodePath:process.execPath});try{let r;switch(e){case"install":r=await c.install({rootDir:o.rootDir,configDir:w});break;case"start":r=await c.start({rootDir:o.rootDir});break;case"stop":r=await c.stop({rootDir:o.rootDir});break;case"restart":r=await c.restart({rootDir:o.rootDir});break;case"uninstall":r=await c.uninstall({rootDir:o.rootDir});break;case"status":r=await c.status({rootDir:o.rootDir});break}console.log(JSON.stringify(r,null,2)),process.exit(0)}catch(r){console.error(`service ${e} failed: ${r instanceof Error?r.message:r}`),process.exit(1)}}const i=g(),N=s["config-dir"]??(s.profile?`${i.configDir}/${s.profile}`:void 0),h=new E,d=new I;let S=!1;async function u(e){if(S)return;S=!0,a.info("main",`Received ${e}, shutting down...`),d.markShuttingDown();const t=setTimeout(()=>{a.error("main","Shutdown timed out, forcing exit"),p(i.daemonLockFile).catch(()=>{}),l(),process.exit(2)},1e4);try{await h.stop(),await d.stop(),await p(i.daemonLockFile),await F(i.daemonStatusFile).catch(()=>{}),clearTimeout(t),l(),a.info("main","Shutdown complete"),process.exit(0)}catch(o){a.error("main",`Shutdown error: ${o}`),p(i.daemonLockFile).catch(()=>{}),l(),process.exit(2)}}async function P(){k(),O();try{await $(i.daemonLockFile,i.rootDir)}catch(t){console.error(t instanceof Error?t.message:t),process.exit(1)}R(),a.info("main",`grix-connector starting (PID ${process.pid})`),await f(i.daemonStatusFile,{state:"starting",pid:process.pid,updated_at:Date.now()});const e=parseInt(s["health-port"]??process.env.GRIX_HEALTH_PORT??"19579",10);await d.start(e),process.on("SIGINT",()=>u("SIGINT")),process.on("SIGTERM",()=>u("SIGTERM")),process.on("uncaughtException",t=>{a.error("main",`Uncaught exception: ${t instanceof Error?t.stack:t}`),!v(t)&&u("uncaughtException")}),process.on("unhandledRejection",t=>{a.error("main",`Unhandled rejection: ${t}`),!v(t)&&u("unhandledRejection")}),d.setStatusProvider(()=>h.getAgentsStatus()),await h.start(N),await f(i.daemonStatusFile,{state:"running",pid:process.pid,updated_at:Date.now()}),process.send&&process.send("ready"),a.info("main","grix-connector ready")}P().catch(e=>{a.error("main",`Fatal: ${e}`),p(i.daemonLockFile).catch(()=>{}),l(),process.exit(1)});const A=new Set(["ECONNRESET","ECONNREFUSED","ETIMEDOUT","EPIPE","EAI_AGAIN","ENOTFOUND","EHOSTUNREACH","ENETUNREACH"]);function v(e){return e instanceof Error&&"code"in e?A.has(e.code):!1}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import*as i from"node:net";const e={bind:"127.0.0.1",port:0,endpoint:"/mcp",sessionTimeoutMs:18e5,invokeTimeoutMs:3e4};function n(o){const u={bind:o?.bind??e.bind,port:o?.port??e.port,endpoint:o?.endpoint??e.endpoint,sessionTimeoutMs:o?.sessionTimeoutMs??e.sessionTimeoutMs,invokeTimeoutMs:o?.invokeTimeoutMs??e.invokeTimeoutMs,allowedOrigins:o?.allowedOrigins,allowedHosts:o?.allowedHosts};return s(u.bind),u.port!==0&&t(u.port),r(u.sessionTimeoutMs),u}function s(o){if(!o||!i.isIPv4(o)&&!i.isIPv6(o))throw new Error(`\u914D\u7F6E\u6821\u9A8C\u5931\u8D25: bind \u5730\u5740 "${o}" \u4E0D\u662F\u5408\u6CD5\u7684 IPv4 \u6216 IPv6 \u5730\u5740`)}function t(o){if(!Number.isInteger(o)||o<1||o>65535)throw new Error(`\u914D\u7F6E\u6821\u9A8C\u5931\u8D25: port \u503C ${o} \u4E0D\u5728\u5408\u6CD5\u8303\u56F4 1-65535 \u5185\u6216\u4E0D\u662F\u6574\u6570`)}function r(o){if(!Number.isInteger(o)||o<1e3||o>864e5)throw new Error(`\u914D\u7F6E\u6821\u9A8C\u5931\u8D25: session_timeout_ms \u503C ${o} \u4E0D\u5728\u5408\u6CD5\u8303\u56F4 1000-86400000 \u5185`)}export{n as createDefaultGatewayConfig};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
const r=3e4;class c{connectionManager;onDisconnected;bindings=new Map;constructor(n,i){this.connectionManager=n,this.onDisconnected=i}async bind(n,i){if(this.bindings.has(n))throw new Error(`Session ${n} is already bound to a connection`);const e=await this.connectWithTimeout(i),t=[],s=e.onDisconnected(()=>{this.removeBinding(n),this.onDisconnected(n)});return t.push(s),this.bindings.set(n,{sessionId:n,handle:e,subscriptions:t}),e}getHandle(n){return this.bindings.get(n)?.handle}unbind(n){const i=this.bindings.get(n);if(i){this.bindings.delete(n);for(const e of i.subscriptions)e();i.handle.disconnect()}}unbindAll(){const n=[...this.bindings.keys()];for(const i of n)this.unbind(i)}connectWithTimeout(n){return new Promise((i,e)=>{let t=!1;const s=setTimeout(()=>{t||(t=!0,e(new Error("Connection bind timeout after 30000ms")))},3e4);this.connectionManager.connect({agentId:n.agentId,apiKey:n.apiKey,url:n.wsUrl,clientType:n.clientType,capabilities:["agent_invoke"],adapterHint:`${n.clientType}/base`},{maxRetries:0}).then(o=>{t?o.disconnect():(t=!0,clearTimeout(s),i(o))}).catch(o=>{t||(t=!0,clearTimeout(s),e(o))})})}removeBinding(n){const i=this.bindings.get(n);if(i){this.bindings.delete(n);for(const e of i.subscriptions)e()}}}export{c as ConnectionBindingImpl};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{splitTextForAibotProtocol as v}from"../../core/protocol/protocol-text.js";const d=new Set(["grix_reply","grix_complete","grix_event_ack","grix_composing","grix_access_control","grix_status"]);function g(n){return d.has(n)}function p(n,e,t){switch(e){case"grix_reply":return f(n,t);case"grix_complete":return x(n,t);case"grix_event_ack":return C(n,t);case"grix_composing":return S(n,t);case"grix_access_control":return m(n,t);case"grix_status":return y(n);default:return r(`\u672A\u77E5\u4E8B\u4EF6\u5DE5\u5177: ${e}`)}}function f(n,e){const t=String(e.event_id??""),s=String(e.session_id??""),i=String(e.text??""),a=e.quoted_message_id,l=e.is_final===!0;if(!s)return r("\u7F3A\u5C11\u5FC5\u586B\u53C2\u6570: session_id");if(!i)return r("\u7F3A\u5C11\u5FC5\u586B\u53C2\u6570: text");const _=v(i),c=`reply_${t||"proactive"}_${Date.now()}`;for(let o=0;o<_.length;o++)n.sendStreamChunk({event_id:t||void 0,session_id:s,delta_content:_[o],chunk_seq:o+1,is_finish:!1,client_msg_id:c,quoted_message_id:o===0?a:void 0});return n.sendStreamChunk({event_id:t||void 0,session_id:s,delta_content:"",chunk_seq:_.length+1,is_finish:!0,client_msg_id:c}),l&&t&&n.sendEventResult({event_id:t,status:"responded"}),u({ok:!0,chunks:_.length,client_msg_id:c})}function x(n,e){const t=String(e.event_id??""),s=String(e.status??""),i=e.msg;return t?["responded","canceled","failed"].includes(s)?(n.sendEventResult({event_id:t,status:s,msg:i}),u({ok:!0,event_id:t,status:s})):r("status \u5FC5\u987B\u4E3A responded\u3001canceled \u6216 failed"):r("\u7F3A\u5C11\u5FC5\u586B\u53C2\u6570: event_id")}function C(n,e){const t=String(e.event_id??""),s=e.session_id;return t?(n.sendEventAck({event_id:t,session_id:s,received_at:Date.now()}),u({ok:!0,event_id:t})):r("\u7F3A\u5C11\u5FC5\u586B\u53C2\u6570: event_id")}function S(n,e){const t=String(e.session_id??""),s=e.active===!0,i=e.event_id;return t?(n.sendSessionActivitySet({session_id:t,kind:"composing",active:s,ref_event_id:i,ttl_ms:s?3e4:void 0}),u({ok:!0,session_id:t,active:s})):r("\u7F3A\u5C11\u5FC5\u586B\u53C2\u6570: session_id")}function m(n,e){const t=String(e.action??""),s={pair_approve:"pair_approve",pair_deny:"pair_deny",allow_sender:"sender_allow",remove_sender:"sender_remove",set_policy:"policy_set"}[t];if(!s)return r(`\u672A\u77E5 access_control action: ${t}`);const i={};return e.code!=null&&(i.code=e.code),e.sender_id!=null&&(i.sender_id=e.sender_id),e.policy!=null&&(i.policy=e.policy),{content:[{type:"text",text:"__ASYNC_ACCESS_CONTROL__"}],_async:{verb:s,payload:i}}}function y(n){const e=n.status,t=n.getStatusSnapshot();return u({connected:e==="ready",status:e,connected_at:t.connectedAt})}function u(n){return{content:[{type:"text",text:JSON.stringify(n)}],isError:!1}}function r(n){return{content:[{type:"text",text:n}],isError:!0}}export{d as EVENT_TOOL_NAMES,p as executeEventTool,g as isEventTool};
|