@lark-tns/openclaw-guardian-plugin 2026.4.15 → 2026.4.19

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/index.js ADDED
@@ -0,0 +1,8 @@
1
+ import{createRequire as e}from"node:module";import{definePluginEntry as t}from"openclaw/plugin-sdk/plugin-entry";import{basename as n,dirname as r,join as i,relative as a,resolve as o}from"node:path";import{homedir as s,hostname as c}from"node:os";import{lstat as l,mkdir as u,readFile as d,readdir as f,rename as p,stat as m,unlink as h,writeFile as g}from"node:fs/promises";import{createHash as _}from"node:crypto";import{existsSync as v}from"node:fs";var y=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),b=e(import.meta.url);const ee=2e4;function x(e){return`[openclaw-guardian-plugin] ${e}`}function S(){let e=new WeakSet;return(t,n)=>{if(n instanceof Error)return{name:n.name,message:n.message,stack:n.stack};if(typeof n==`bigint`)return n.toString();if(typeof n==`object`&&n){if(e.has(n))return`[Circular]`;e.add(n)}return n}}function C(e){let t=JSON.stringify(e,S(),2)??`null`;return t.length<=ee?t:`${t.slice(0,ee)}\n...<truncated>`}function w(e,t){return t===`online`?`[payload omitted in online]`:C(e)}function T(e,t,n,r){e.logger.info(x(`[${t}] ${w(n,r)}`))}function te(e){return typeof e==`string`?e:typeof e==`number`||typeof e==`boolean`||typeof e==`bigint`?String(e):JSON.stringify(e,S(),2)??`null`}function E(e){let t=e?.pluginConfig?.env;return t===`boe`||t===`dev`||t===`pre`?t:`online`}let D=null,O=null;function k(e,t){if(typeof e!=`string`||!e)throw Error(`missing or invalid ${t}`);return e}function A(...e){for(let t of e)if(typeof t==`string`&&t)return t;return null}function j(e){return!e||typeof e!=`object`||Array.isArray(e)?null:e}function M(e){let t=A(e?.pluginConfig?.appId),n=A(e?.pluginConfig?.appSecret),r=t&&n?{appId:t,appSecret:n}:null,i=j(j(j(e?.config)?.channels)?.feishu),a=A(i?.appId,i?.app_id),o=A(i?.appSecret,i?.app_secret),s=a&&o?{appId:a,appSecret:o}:null,c=E(e);return c===`online`||c===`pre`?s??r??null:r??s??null}function N(e){return!!M(e)}function P(e){let t=e?.pluginConfig?.x_tt_env;return typeof t==`string`&&t?t:E(e)===`boe`?`boe_tns_api`:null}function F(e){let t=E(e);return t===`online`?`https://open.feishu.cn`:t===`pre`?`https://open.feishu-pre.cn`:`https://open.feishu-boe.cn`}function ne(e){if(!e||typeof e!=`object`||Array.isArray(e))return null;let t=e;return{code:typeof t.code==`number`?t.code:NaN,msg:typeof t.msg==`string`?t.msg:``,tenant_access_token:typeof t.tenant_access_token==`string`?t.tenant_access_token:void 0,expire:typeof t.expire==`number`?t.expire:void 0}}async function I(e){let t=Date.now();return D&&D.expiresAtMs-t>18e5?D.token:O||(O=(async()=>{let t=M(e);if(!t)throw Error(`missing Feishu appId/appSecret from api.config.channels.feishu or pluginConfig`);let n=k(t.appId,`appId`),r=k(t.appSecret,`appSecret`),i=`${F(e)}/open-apis/auth/v3/tenant_access_token/internal`,a={"content-type":`application/json`},o=P(e);o&&(a[`x-tt-env`]=o),e.logger.debug?.(x(`[auth] requesting tenant_access_token`));let{signal:s,clear:c}=ie(U),l;try{l=await fetch(i,{method:`POST`,headers:a,body:JSON.stringify({app_id:n,app_secret:r}),signal:s})}catch(e){throw c(),e instanceof Error&&e.name===`AbortError`?new z(i,U):new B(i,e)}finally{c()}let u=await l.text();if(!l.ok)throw Error(`[auth] tenant_access_token request failed status=${l.status} body=${u||`<empty>`}`);let d=null;try{d=u?JSON.parse(u):null}catch{d=null}let f=ne(d);if(!f||!Number.isFinite(f.code))throw Error(`[auth] tenant_access_token response parse failed`);if(f.code!==0)throw Error(`[auth] tenant_access_token request failed code=${f.code}${f.msg?` msg=${f.msg}`:``}`);let p=k(f.tenant_access_token,`tenant_access_token`),m=typeof f.expire==`number`&&f.expire>0?f.expire:7200;return D={token:p,expiresAtMs:Date.now()+m*1e3},e.logger.debug?.(x(`[auth] tenant_access_token updated`)),p})().finally(()=>{O=null}),O)}async function L(e){if(E(e)===`dev`)return{};let t={},n=P(e);return n&&(t[`x-tt-env`]=n),t.authorization=`Bearer ${await I(e)}`,t}async function R(e,t={}){let n=await L(e),r=new Headers(t.headers??{});for(let[e,t]of Object.entries(n))r.set(e,t);return{...t,headers:r}}var z=class extends Error{code=`TIMEOUT`;constructor(e,t){super(`Request to ${e} timed out after ${t}ms`),this.name=`RequestTimeoutError`}},B=class extends Error{code=`NETWORK`;constructor(e,t){super(`Network error requesting ${e}: ${t instanceof Error?t.message:String(t)}`),this.name=`NetworkError`}},re=class extends Error{code=`SERVER`;statusCode;constructor(e,t,n){super(`Server error ${t} from ${e}${n?`: ${n}`:``}`),this.name=`ServerError`,this.statusCode=t}};function V(e){return e instanceof z?`TIMEOUT`:e instanceof B?`NETWORK`:e instanceof re?`SERVER`:e instanceof Error&&e.name===`AbortError`?`TIMEOUT`:`UNKNOWN`}const H=3e4,U=3e4;function ie(e){let t=new AbortController,n=setTimeout(()=>t.abort(),e);return typeof n==`object`&&`unref`in n&&n.unref(),{signal:t.signal,clear:()=>clearTimeout(n)}}function ae(e,t){if(!e)return t;let n=new AbortController,r=()=>n.abort();return e.addEventListener(`abort`,r,{once:!0}),t.addEventListener(`abort`,r,{once:!0}),n.signal}const oe={dev:`http://localhost:8001`,online:`https://open.feishu.cn`,boe:`https://open.feishu-boe.cn`,pre:`https://open.feishu-pre.cn`};function W(e){return`${oe[E(e)]}/open-apis/security_plugin/v1/openclaw_plugin`}function se(e){return`${W(e)}/config`}function ce(e){return`${W(e)}/detect`}function le(e){return`${W(e)}/batch_check_skill_detection`}function ue(e){return`${W(e)}/skill_detect`}function de(e={}){let t={};for(let[n,r]of Object.entries(e??{}))t[n]=te(r);return t}function fe(e,t,n=``){return{hook_name:e,payload:de(t),source:n}}function pe(e){if(!e)return null;try{let t=JSON.parse(e);return t&&typeof t==`object`&&!Array.isArray(t)?t:null}catch{return null}}function me(e){if(!e||typeof e!=`object`||Array.isArray(e))return!1;let t=e;return typeof t.code==`number`&&Number.isFinite(t.code)&&typeof t.msg==`string`}async function he(e,t,n){let r=await n.text();if(e.logger.debug?.(x(`[${t}] response: ${r}`)),!n.ok)throw Error(`${t} request failed: status=${n.status} body=${r||`<empty>`}`);let i=pe(r);if(!me(i))throw Error(`${t} response parse failed: missing or invalid code/msg fields`);if(i.code!==0)throw Error(`${t} request failed: code=${i.code}${i.msg?` msg=${i.msg}`:``}`);return i.data??null}async function G(e,t,n,r,i=H){let a=await R(e,r),{signal:o,clear:s}=ie(i),c=ae(a.signal,o),l,u=Date.now();try{l=await fetch(n,{...a,signal:c});let r=Date.now()-u;e.logger.info(x(`[${t}] http_elapsed=${r}ms status=${l.status} url=${n}`))}catch(r){let a=Date.now()-u;throw e.logger.info(x(`[${t}] http_elapsed=${a}ms error=${r instanceof Error?r.message:String(r)} url=${n}`)),s(),r instanceof Error&&r.name===`AbortError`?new z(n,i):new B(n,r)}finally{s()}return he(e,t,l)}let ge=null;function _e(){return ge||=c(),ge}function ve(){return{hostname:_e()}}function ye(e){if(!e||typeof e!=`object`||Array.isArray(e))return e;let t=e;return Object.prototype.hasOwnProperty.call(t,`hostname`)?e:{...t,...ve()}}async function K(e,t,n){let r=fe(t,ye(n),`miaoda`);return G(e,t,ce(e),{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify(r)},H)}function q(...e){return e.find(e=>e!=null)}function J(e){return e==null?``:typeof e==`string`?e:String(e)}function be(e){return e&&typeof e==`object`&&!Array.isArray(e)?e:{}}function Y(e){return Array.isArray(e)?e:[]}function xe(e){try{let t=new URL(e);return{domain:t.host,path:t.pathname||`/`}}catch{return{domain:``,path:``}}}function Se(e){let{domain:t,path:n}=xe(J(e?.url));return{request_id:J(e?.request_id),domain:t,path:n,origin_req:q(e?.jsonBody,e?.rawBody,``)}}function Ce(e){let{domain:t,path:n}=xe(J(e?.url));return{request_id:J(e?.request_id),domain:t,path:n,is_sse:e?.isSse?`true`:`false`,chunks:Y(e?.chunks),origin_resp:e?.isSse?``:J(q(e?.originResp,``))}}function we(e,t){return{agent_name:J(t?.agentId),session_id:J(t?.sessionId),session_key:J(t?.sessionKey),channel_id:J(t?.channelId),message_provider:J(t?.messageProvider),trigger:J(t?.trigger),model:J(e?.model),provider:J(e?.provider),run_id:J(e?.runId)}}function Te(e,t){return{...we(e,t),prompt:J(e?.prompt),system_prompt:J(e?.systemPrompt),history_messages:Y(e?.historyMessages)}}function Ee(e,t){return{...we(e,t),assistant_texts:Y(e?.assistantTexts),last_assistant:J(JSON.stringify(e?.lastAssistant))}}function De(e,t){return{agent_name:J(t?.agentId),session_id:J(t?.sessionId),session_key:J(t?.sessionKey),tool_call_id:J(e?.toolCallId),tool_name:J(e?.toolName),tool_args:q(e?.params,{}),run_id:J(e?.runId)}}function Oe(e,t){return De(e,t)}function ke(e,t){return{...De(e,t),tool_output:q(e?.result,``),tool_error:q(e?.error,``),tool_duration_ms:J(q(e?.durationMs,``))}}function X(e,t,n,r){let i=t,a=t,o=n;switch(e){case`before_tool_call`:return Oe(i,o);case`after_tool_call`:return ke(i,o);case`before_llm_fetch`:return Se(r);case`after_llm_fetch`:return Ce(r);case`llm_input`:return Te(a,o);case`llm_output`:return Ee(a,o);default:return be(r)}}function Ae(e){return i(e??i(s(),`.openclaw`,`workspace`),`.openclaw-guardian`,`skill-detect-state.json`)}const je=Ae();function Me(e){if(!e||typeof e!=`object`||!(`code`in e))return;let{code:t}=e;return typeof t==`string`?t:void 0}function Ne(e,t){return e.is_detected?typeof e.last_detect_ts==`number`?(t??Date.now())-e.last_detect_ts>864e5:!0:!1}function Pe(e){return!e.skill_hash}let Fe=Promise.resolve();function Ie(e){let t=Fe.then(e,e);return Fe=t.catch(()=>void 0),t}async function Le(e){try{await h(e)}catch{}}async function Re(e,t){return Ie(async()=>{let n=t??je;e.logger.info(x(`[skill-storage] loading state from: ${n}`));try{let t=await d(n,`utf-8`),r=JSON.parse(t);return!r||typeof r!=`object`||Array.isArray(r)?(e.logger.warn(x(`[skill-storage] corrupted state file (invalid format), deleting and starting fresh`)),await Le(n),{}):(e.logger.info(x(`[skill-storage] loaded ${Object.keys(r).length} skill(s) from state`)),r)}catch(t){return Me(t)===`ENOENT`?(e.logger.info(x(`[skill-storage] no existing state file, starting fresh`)),{}):t instanceof SyntaxError?(e.logger.warn(x(`[skill-storage] corrupted state file (invalid JSON), deleting and starting fresh`)),await Le(n),{}):(e.logger.warn(x(`[skill-storage] failed to load state: ${t instanceof Error?t.message:String(t)}`)),{})}})}async function ze(e,t,n){return Ie(async()=>{let i=n??je,a=`${i}.tmp`;e.logger.info(x(`[skill-storage] saving state (${Object.keys(t).length} skill(s)) to: ${i}`));try{await u(r(i),{recursive:!0}),await g(a,JSON.stringify(t,null,2),`utf-8`),await p(a,i),e.logger.info(x(`[skill-storage] state saved successfully`))}catch(t){e.logger.error(x(`[skill-storage] failed to save state: ${t instanceof Error?t.message:String(t)}`)),await Le(a)}})}function Be(e,t,n){let r={...e},i=n??Date.now();for(let e of t){let t=r[e.dirName];t&&t.skill_hash===e.skillHash?Ne(t,i)?r[e.dirName]={...t,skill_name:e.skillName,is_detected:!1}:r[e.dirName]={...t,skill_name:e.skillName}:r[e.dirName]={skill_name:e.skillName,skill_hash:e.skillHash,is_detected:!1,last_detect_ts:null}}let a=new Set(t.map(e=>e.dirName));for(let e of Object.keys(r))a.has(e)||delete r[e];return r}var Ve=y(((e,t)=>{function n(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,`default`)?e.default:e}let r=new Int32Array([0,1996959894,3993919788,2567524794,124634137,1886057615,3915621685,2657392035,249268274,2044508324,3772115230,2547177864,162941995,2125561021,3887607047,2428444049,498536548,1789927666,4089016648,2227061214,450548861,1843258603,4107580753,2211677639,325883990,1684777152,4251122042,2321926636,335633487,1661365465,4195302755,2366115317,997073096,1281953886,3579855332,2724688242,1006888145,1258607687,3524101629,2768942443,901097722,1119000684,3686517206,2898065728,853044451,1172266101,3705015759,2882616665,651767980,1373503546,3369554304,3218104598,565507253,1454621731,3485111705,3099436303,671266974,1594198024,3322730930,2970347812,795835527,1483230225,3244367275,3060149565,1994146192,31158534,2563907772,4023717930,1907459465,112637215,2680153253,3904427059,2013776290,251722036,2517215374,3775830040,2137656763,141376813,2439277719,3865271297,1802195444,476864866,2238001368,4066508878,1812370925,453092731,2181625025,4111451223,1706088902,314042704,2344532202,4240017532,1658658271,366619977,2362670323,4224994405,1303535960,984961486,2747007092,3569037538,1256170817,1037604311,2765210733,3554079995,1131014506,879679996,2909243462,3663771856,1141124467,855842277,2852801631,3708648649,1342533948,654459306,3188396048,3373015174,1466479909,544179635,3110523913,3462522015,1591671054,702138776,2966460450,3352799412,1504918807,783551873,3082640443,3233442989,3988292384,2596254646,62317068,1957810842,3939845945,2647816111,81470997,1943803523,3814918930,2489596804,225274430,2053790376,3826175755,2466906013,167816743,2097651377,4027552580,2265490386,503444072,1762050814,4150417245,2154129355,426522225,1852507879,4275313526,2312317920,282753626,1742555852,4189708143,2394877945,397917763,1622183637,3604390888,2714866558,953729732,1340076626,3518719985,2797360999,1068828381,1219638859,3624741850,2936675148,906185462,1090812512,3747672003,2825379669,829329135,1181335161,3412177804,3160834842,628085408,1382605366,3423369109,3138078467,570562233,1426400815,3317316542,2998733608,733239954,1555261956,3268935591,3050360625,752459403,1541320221,2607071920,3965973030,1969922972,40735498,2617837225,3943577151,1913087877,83908371,2512341634,3803740692,2075208622,213261112,2463272603,3855990285,2094854071,198958881,2262029012,4057260610,1759359992,534414190,2176718541,4139329115,1873836001,414664567,2282248934,4279200368,1711684554,285281116,2405801727,4167216745,1634467795,376229701,2685067896,3608007406,1308918612,956543938,2808555105,3495958263,1231636301,1047427035,2932959818,3654703836,1088359270,936918e3,2847714899,3736837829,1202900863,817233897,3183342108,3401237130,1404277552,615818150,3134207493,3453421203,1423857449,601450431,3009837614,3294710456,1567103746,711928724,3020668471,3272380065,1510334235,755167117]);function i(e){if(Buffer.isBuffer(e))return e;if(typeof e==`number`)return Buffer.alloc(e);if(typeof e==`string`)return Buffer.from(e);throw Error(`input must be buffer, number, or string, received `+typeof e)}function a(e){let t=i(4);return t.writeInt32BE(e,0),t}function o(e,t){e=i(e),Buffer.isBuffer(t)&&(t=t.readUInt32BE(0));let n=~~t^-1;for(var a=0;a<e.length;a++)n=r[(n^e[a])&255]^n>>>8;return n^-1}function s(){return a(o.apply(null,arguments))}s.signed=function(){return o.apply(null,arguments)},s.unsigned=function(){return o.apply(null,arguments)>>>0},t.exports=n(s)})),He=y((e=>{var t=b(`fs`),n=b(`stream`).Transform,r=b(`stream`).PassThrough,i=b(`zlib`),a=b(`util`),o=b(`events`).EventEmitter,s=b(`events`).errorMonitor,c=Ve();e.ZipFile=l,a.inherits(l,o);function l(){this.outputStream=new r,this.entries=[],this.outputStreamCursor=0,this.ended=!1,this.allDone=!1,this.forceZip64Eocd=!1,this.errored=!1,this.on(s,function(){this.errored=!0})}l.prototype.addFile=function(e,n,r){var i=this;if(n=x(n,!1),r??={},!g(i)){var a=new C(n,!1,r);i.entries.push(a),t.stat(e,function(n,o){if(n)return i.emit(`error`,n);if(!o.isFile())return i.emit(`error`,Error(`not a file: `+e));a.uncompressedSize=o.size,r.mtime??a.setLastModDate(o.mtime),r.mode??a.setFileAttributesMode(o.mode),a.setFileDataPumpFunction(function(){var n=t.createReadStream(e);a.state=C.FILE_DATA_IN_PROGRESS,n.on(`error`,function(e){i.emit(`error`,e)}),f(i,a,n)}),m(i)})}},l.prototype.addReadStream=function(e,t,n){this.addReadStreamLazy(t,n,function(t){t(null,e)})},l.prototype.addReadStreamLazy=function(e,t,n){var r=this;if(typeof t==`function`&&(n=t,t=null),t??={},e=x(e,!1),!g(r)){var i=new C(e,!1,t);r.entries.push(i),i.setFileDataPumpFunction(function(){i.state=C.FILE_DATA_IN_PROGRESS,n(function(e,t){if(e)return r.emit(`error`,e);f(r,i,t)})}),m(r)}},l.prototype.addBuffer=function(e,t,n){var r=this;if(t=x(t,!1),e.length>1073741823)throw Error(`buffer too large: `+e.length+` > 1073741823`);if(n??={},n.size!=null)throw Error(`options.size not allowed`);if(g(r))return;var a=new C(t,!1,n);a.uncompressedSize=e.length,a.crc32=c.unsigned(e),a.crcAndFileSizeKnown=!0,r.entries.push(a),a.compressionLevel===0?o(e):i.deflateRaw(e,{level:a.compressionLevel},function(e,t){o(t)});function o(e){a.compressedSize=e.length,a.setFileDataPumpFunction(function(){d(r,e),d(r,a.getDataDescriptor()),a.state=C.FILE_DATA_DONE,setImmediate(function(){m(r)})}),m(r)}},l.prototype.addEmptyDirectory=function(e,t){var n=this;if(e=x(e,!0),t??={},t.size!=null)throw Error(`options.size not allowed`);if(t.compress!=null)throw Error(`options.compress not allowed`);if(t.compressionLevel!=null)throw Error(`options.compressionLevel not allowed`);if(!g(n)){var r=new C(e,!0,t);n.entries.push(r),r.setFileDataPumpFunction(function(){d(n,r.getDataDescriptor()),r.state=C.FILE_DATA_DONE,m(n)}),m(n)}};var u=H([80,75,5,6]);l.prototype.end=function(e,t){if(typeof e==`function`&&(t=e,e=null),e??={},!this.ended&&(this.ended=!0,!this.errored)){if(this.calculatedTotalSizeCallback=t,this.forceZip64Eocd=!!e.forceZip64Format,e.comment){if(typeof e.comment==`string`?this.comment=re(e.comment):this.comment=e.comment,this.comment.length>65535)throw Error(`comment is too large`);if(U(this.comment,u))throw Error(`comment contains end of central directory record signature`)}else this.comment=S;m(this)}};function d(e,t){e.outputStream.write(t),e.outputStreamCursor+=t.length}function f(e,t,n){var a=new R,o=new L,s=t.compressionLevel===0?new r:new i.DeflateRaw({level:t.compressionLevel}),c=new L;n.pipe(a).pipe(o).pipe(s).pipe(c).pipe(e.outputStream,{end:!1}),c.on(`end`,function(){if(t.crc32=a.crc32,t.uncompressedSize==null)t.uncompressedSize=o.byteCount;else if(t.uncompressedSize!==o.byteCount)return e.emit(`error`,Error(`file data stream has unexpected number of bytes`));t.compressedSize=c.byteCount,e.outputStreamCursor+=t.compressedSize,d(e,t.getDataDescriptor()),t.state=C.FILE_DATA_DONE,m(e)})}function p(e){if(e.compress!=null&&e.compressionLevel!=null&&!!e.compress!=!!e.compressionLevel)throw Error(`conflicting settings for compress and compressionLevel`);return e.compressionLevel==null?e.compress===!1?0:6:e.compressionLevel}function m(e){if(e.allDone||e.errored)return;if(e.ended&&e.calculatedTotalSizeCallback!=null){var t=h(e);t!=null&&(e.calculatedTotalSizeCallback(t),e.calculatedTotalSizeCallback=null)}var n=r();function r(){for(var t=0;t<e.entries.length;t++){var n=e.entries[t];if(n.state<C.FILE_DATA_DONE)return n}return null}if(n!=null){if(n.state<C.READY_TO_PUMP_FILE_DATA||n.state===C.FILE_DATA_IN_PROGRESS)return;n.relativeOffsetOfLocalHeader=e.outputStreamCursor,d(e,n.getLocalFileHeader()),n.doFileDataPump()}else e.ended&&(e.offsetOfStartOfCentralDirectory=e.outputStreamCursor,e.entries.forEach(function(t){d(e,t.getCentralDirectoryRecord())}),d(e,ee(e)),e.outputStream.end(),e.allDone=!0)}function h(e){for(var t=0,n=0,r=0;r<e.entries.length;r++){var i=e.entries[r];if(i.compressionLevel!==0)return-1;if(i.state>=C.READY_TO_PUMP_FILE_DATA){if(i.uncompressedSize==null)return-1}else if(i.uncompressedSize==null)return null;i.relativeOffsetOfLocalHeader=t;var a=i.useZip64Format();t+=w+i.utf8FileName.length,t+=i.uncompressedSize,i.crcAndFileSizeKnown||(a?t+=A:t+=k),n+=j+i.utf8FileName.length+i.fileComment.length,i.forceDosTimestamp||(n+=M),a&&(n+=N)}var o=0;return(e.forceZip64Eocd||e.entries.length>=65535||n>=65535||t>=4294967295)&&(o+=_+v),o+=y+e.comment.length,t+n+o}function g(e){if(e.ended)throw Error(`cannot add entries after calling end()`);return!!e.errored}var _=56,v=20,y=22;function ee(e,t){var n=!1,r=e.entries.length;(e.forceZip64Eocd||e.entries.length>=65535)&&(r=65535,n=!0);var i=e.outputStreamCursor-e.offsetOfStartOfCentralDirectory,a=i;(e.forceZip64Eocd||i>=4294967295)&&(a=4294967295,n=!0);var o=e.offsetOfStartOfCentralDirectory;if((e.forceZip64Eocd||e.offsetOfStartOfCentralDirectory>=4294967295)&&(o=4294967295,n=!0),t)return n?_+v+y:y;var s=V(y+e.comment.length);if(s.writeUInt32LE(101010256,0),s.writeUInt16LE(0,4),s.writeUInt16LE(0,6),s.writeUInt16LE(r,8),s.writeUInt16LE(r,10),s.writeUInt32LE(a,12),s.writeUInt32LE(o,16),s.writeUInt16LE(e.comment.length,20),e.comment.copy(s,22),!n)return s;var c=V(_);c.writeUInt32LE(101075792,0),I(c,_-12,4),c.writeUInt16LE(E,12),c.writeUInt16LE(te,14),c.writeUInt32LE(0,16),c.writeUInt32LE(0,20),I(c,e.entries.length,24),I(c,e.entries.length,32),I(c,i,40),I(c,e.offsetOfStartOfCentralDirectory,48);var l=V(v);return l.writeUInt32LE(117853008,0),l.writeUInt32LE(0,4),I(l,e.outputStreamCursor,8),l.writeUInt32LE(1,16),Buffer.concat([c,l,s])}function x(e,t){if(e===``)throw Error(`empty metadataPath`);if(e=e.replace(/\\/g,`/`),/^[a-zA-Z]:/.test(e)||/^\//.test(e))throw Error(`absolute path: `+e);if(e.split(`/`).indexOf(`..`)!==-1)throw Error(`invalid relative path: `+e);var n=/\/$/.test(e);if(t)n||(e+=`/`);else if(n)throw Error(`file path cannot end with '/': `+e);return e}var S=V(0);function C(e,t,n){if(this.utf8FileName=H(e),this.utf8FileName.length>65535)throw Error(`utf8 file name too long. `+utf8FileName.length+` > 65535`);if(this.isDirectory=t,this.state=C.WAITING_FOR_METADATA,this.setLastModDate(n.mtime==null?new Date:n.mtime),this.forceDosTimestamp=!!n.forceDosTimestamp,n.mode==null?this.setFileAttributesMode(t?16893:33204):this.setFileAttributesMode(n.mode),t?(this.crcAndFileSizeKnown=!0,this.crc32=0,this.uncompressedSize=0,this.compressedSize=0):(this.crcAndFileSizeKnown=!1,this.crc32=null,this.uncompressedSize=null,this.compressedSize=null,n.size!=null&&(this.uncompressedSize=n.size)),t?this.compressionLevel=0:this.compressionLevel=p(n),this.forceZip64Format=!!n.forceZip64Format,n.fileComment){if(typeof n.fileComment==`string`?this.fileComment=H(n.fileComment,`utf-8`):this.fileComment=n.fileComment,this.fileComment.length>65535)throw Error(`fileComment is too large`)}else this.fileComment=S}C.WAITING_FOR_METADATA=0,C.READY_TO_PUMP_FILE_DATA=1,C.FILE_DATA_IN_PROGRESS=2,C.FILE_DATA_DONE=3,C.prototype.setLastModDate=function(e){this.mtime=e;var t=ne(e);this.lastModFileTime=t.time,this.lastModFileDate=t.date},C.prototype.setFileAttributesMode=function(e){if((e&65535)!==e)throw Error(`invalid mode. expected: 0 <= `+e+` <= 65535`);this.externalFileAttributes=e<<16>>>0},C.prototype.setFileDataPumpFunction=function(e){this.doFileDataPump=e,this.state=C.READY_TO_PUMP_FILE_DATA},C.prototype.useZip64Format=function(){return this.forceZip64Format||this.uncompressedSize!=null&&this.uncompressedSize>4294967294||this.compressedSize!=null&&this.compressedSize>4294967294||this.relativeOffsetOfLocalHeader!=null&&this.relativeOffsetOfLocalHeader>4294967294};var w=30,T=20,te=45,E=831,D=2048,O=8;C.prototype.getLocalFileHeader=function(){var e=0,t=0,n=0;this.crcAndFileSizeKnown&&(e=this.crc32,t=this.compressedSize,n=this.uncompressedSize);var r=V(w),i=D;return this.crcAndFileSizeKnown||(i|=O),r.writeUInt32LE(67324752,0),r.writeUInt16LE(T,4),r.writeUInt16LE(i,6),r.writeUInt16LE(this.getCompressionMethod(),8),r.writeUInt16LE(this.lastModFileTime,10),r.writeUInt16LE(this.lastModFileDate,12),r.writeUInt32LE(e,14),r.writeUInt32LE(t,18),r.writeUInt32LE(n,22),r.writeUInt16LE(this.utf8FileName.length,26),r.writeUInt16LE(0,28),Buffer.concat([r,this.utf8FileName])};var k=16,A=24;C.prototype.getDataDescriptor=function(){if(this.crcAndFileSizeKnown)return S;if(this.useZip64Format()){var e=V(A);return e.writeUInt32LE(134695760,0),e.writeUInt32LE(this.crc32,4),I(e,this.compressedSize,8),I(e,this.uncompressedSize,16),e}else{var e=V(k);return e.writeUInt32LE(134695760,0),e.writeUInt32LE(this.crc32,4),e.writeUInt32LE(this.compressedSize,8),e.writeUInt32LE(this.uncompressedSize,12),e}};var j=46,M=9,N=28;C.prototype.getCentralDirectoryRecord=function(){var e=V(j),t=D;this.crcAndFileSizeKnown||(t|=O);var n=S;if(!this.forceDosTimestamp){n=V(M),n.writeUInt16LE(21589,0),n.writeUInt16LE(M-4,2),n.writeUInt8(3,4);var r=Math.floor(this.mtime.getTime()/1e3);r<-2147483648&&(r=-2147483648),r>2147483647&&(r=2147483647),n.writeUInt32LE(r,5)}var i=this.compressedSize,a=this.uncompressedSize,o=this.relativeOffsetOfLocalHeader,s=T,c=S;return this.useZip64Format()&&(i=4294967295,a=4294967295,o=4294967295,s=te,c=V(N),c.writeUInt16LE(1,0),c.writeUInt16LE(N-4,2),I(c,this.uncompressedSize,4),I(c,this.compressedSize,12),I(c,this.relativeOffsetOfLocalHeader,20)),e.writeUInt32LE(33639248,0),e.writeUInt16LE(E,4),e.writeUInt16LE(s,6),e.writeUInt16LE(t,8),e.writeUInt16LE(this.getCompressionMethod(),10),e.writeUInt16LE(this.lastModFileTime,12),e.writeUInt16LE(this.lastModFileDate,14),e.writeUInt32LE(this.crc32,16),e.writeUInt32LE(i,20),e.writeUInt32LE(a,24),e.writeUInt16LE(this.utf8FileName.length,28),e.writeUInt16LE(n.length+c.length,30),e.writeUInt16LE(this.fileComment.length,32),e.writeUInt16LE(0,34),e.writeUInt16LE(0,36),e.writeUInt32LE(this.externalFileAttributes,38),e.writeUInt32LE(o,42),Buffer.concat([e,this.utf8FileName,n,c,this.fileComment])},C.prototype.getCompressionMethod=function(){return this.compressionLevel===0?0:8};var P=new Date(1980,0,1),F=new Date(2107,11,31,23,59,58);function ne(e){e<P?e=P:e>F&&(e=F);var t=0;t|=e.getDate()&31,t|=(e.getMonth()+1&15)<<5,t|=(e.getFullYear()-1980&127)<<9;var n=0;return n|=Math.floor(e.getSeconds()/2),n|=(e.getMinutes()&63)<<5,n|=(e.getHours()&31)<<11,{date:t,time:n}}function I(e,t,n){var r=Math.floor(t/4294967296),i=t%4294967296;e.writeUInt32LE(i,n),e.writeUInt32LE(r,n+4)}a.inherits(L,n);function L(e){n.call(this,e),this.byteCount=0}L.prototype._transform=function(e,t,n){this.byteCount+=e.length,n(null,e)},a.inherits(R,n);function R(e){n.call(this,e),this.crc32=0}R.prototype._transform=function(e,t,n){this.crc32=c.unsigned(e,this.crc32),n(null,e)};var z=`\0☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_\`abcdefghijklmnopqrstuvwxyz{|}~⌂ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■\xA0`;if(z.length!==256)throw Error(`assertion failure`);var B=null;function re(e){if(/^[\x20-\x7e]*$/.test(e))return H(e,`utf-8`);if(B==null){B={};for(var t=0;t<z.length;t++)B[z[t]]=t}for(var n=V(e.length),t=0;t<e.length;t++){var r=B[e[t]];if(r==null)throw Error(`character not encodable in CP437: `+JSON.stringify(e[t]));n[t]=r}return n}function V(e){V=t;try{return V(e)}catch{return V=n,V(e)}function t(e){return Buffer.allocUnsafe(e)}function n(e){return new Buffer(e)}}function H(e,t){H=n;try{return H(e,t)}catch{return H=r,H(e,t)}function n(e,t){return Buffer.from(e,t)}function r(e,t){return new Buffer(e,t)}}function U(e,t){U=n;try{return U(e,t)}catch{return U=r,U(e,t)}function n(e,t){return e.includes(t)}function r(e,t){for(var n=0;n<=e.length-t.length;n++)for(var r=0;;r++){if(r===t.length)return!0;if(e[n+r]!==t[r])break}return!1}}}))();function Z(e){return`v1:${e}`}function Ue(e){if(!e||typeof e!=`object`||Array.isArray(e))return{};let t={};for(let[n,r]of Object.entries(e))t[n]=r===!0;return t}function We(e){return{skills:Object.values(e).map(e=>({skill_name:e.skill_name,skill_hash:Z(e.skill_hash)}))}}async function Ge(e,t){let n=le(e),r=We(t),i=await G(e,`batch_check_detection`,n,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify(r)},H);return i&&typeof i==`object`&&!Array.isArray(i)&&`result`in i?Ue(i.result):Ue(i)}const Ke=10*1024*1024;async function qe(e){let t=await f(e),n=[];for(let r of t){let t=i(e,r),a=await l(t);a.isSymbolicLink()||(a.isDirectory()?n.push(...await qe(t)):a.isFile()&&n.push(t))}return n}async function Je(e){let t=await m(e).catch(()=>null);if(!t||!t.isDirectory())throw Error(`zipDirectory: path does not exist or is not a directory: ${e}`);let n=await qe(e);return n.sort((t,n)=>{let r=a(e,t),i=a(e,n);return r<i?-1:+(r>i)}),new Promise((t,r)=>{let i=new He.ZipFile,o=[],s=0,c=!1,l=e=>{c||(c=!0,r(e))};i.outputStream.on(`data`,t=>{if(!c){if(s+=t.length,s>Ke){l(Error(`zipDirectory: zip exceeds ${Ke} bytes limit for ${e} (${s} bytes)`));return}o.push(t)}}),i.outputStream.on(`end`,()=>{c||(c=!0,t(Buffer.concat(o)))}),i.outputStream.on(`error`,l);for(let t of n)i.addFile(t,a(e,t));i.end()})}async function Ye(e,t,n){let r=ue(e),i=`skill_detect:${n.skill_name}`;e.logger.info(x(`[skill-detect] detect: sending skill=${n.skill_name} hash=${n.skill_hash.slice(0,12)}... dir=${t}`));let a=await Je(t);e.logger.info(x(`[skill-detect] zipped ${t} (${a.length} bytes)`));let o=new FormData,s=new Blob([new Uint8Array(a)],{type:`application/zip`});return o.append(`file`,s,n.skill_name),o.append(`skill`,JSON.stringify({skill_name:n.skill_name,skill_hash:Z(n.skill_hash)})),o.append(`payload`,JSON.stringify(ve())),await G(e,i,r,{method:`POST`,body:o},6e5),e.logger.info(x(`[skill-detect] skill ${n.skill_name} detected successfully`)),!0}function Xe(e){if(e.tool_name!==`read`)return null;let t=e.tool_args;return!t||typeof t!=`object`||Array.isArray(t)?null:Object.values(t).find(e=>typeof e==`string`&&e.endsWith(`SKILL.md`))??null}async function Ze(e,t,i){let a=n(r(t));try{let t;i&&(t=Ae(e.runtime.agent.resolveAgentWorkspaceDir(e.config,i)));let n=(await Re(e,t))[a];return!n||!n.skill_hash?null:{skill_name:n.skill_name,skill_hash:Z(n.skill_hash)}}catch{return null}}function Qe(...e){return e.find(e=>typeof e==`string`)??``}function $e(e){return typeof e==`string`?e.toUpperCase():`PERMIT`}function et(e){if(!(typeof e!=`string`||e.length===0))try{let t=JSON.parse(e);return t&&typeof t==`object`&&!Array.isArray(t)?t:void 0}catch{return}}function tt(e){return{effect:$e(e?.effect),denyOutput:Qe(e?.deny_output),rewriteOutput:Qe(e?.rewrite_output)}}function nt(e){let t=$e(e.effect);if(t===`DENY`)return e.denyOutput?{action:`deny`,output:e.denyOutput,returnValue:{block:!0,blockReason:e.denyOutput||`Tool call blocked by detect service`}}:{action:`permit`,returnValue:void 0};if(t===`REWRITE`){if(!e.rewriteOutput)return{action:`permit`,returnValue:void 0};let t=et(e.rewriteOutput);return t?{action:`rewrite`,returnValue:{params:t}}:{action:`rewrite_invalid`,returnValue:void 0}}return{action:`permit`,returnValue:void 0}}async function rt(e,t,n,r,i){if(!await t.isHookEnabled(n)){e.logger.info(x(`[${n}] detect skipped: hook disabled by runtime config`));return}let a=Date.now(),o=X(n,r,i);T(e,n,o,E(e));try{let t=tt(await K(e,n,o));e.logger.info(x(`[${n}] detect effect observed: effect=${t.effect} (report-only hook)`))}catch(t){e.logger.error(x(`[${n}] detect request failed: ${t instanceof Error?t.message:String(t)}`))}finally{let t=Date.now()-a;e.logger.info(x(`[${n}] total_elapsed=${t}ms`))}}async function it(e,t,n,r){let i=Date.now();if(!await t.isHookEnabled(`before_tool_call`)){e.logger.info(x(`[before_tool_call] detect skipped: hook disabled by runtime config`));return}let a=X(`before_tool_call`,n,r),o=Xe(a),s=a;if(o){let t=await Ze(e,o,r.agentId);t&&(s={...s,...t},e.logger.info(x(`[before_tool_call] skill read detected: skill=${t.skill_name} hash=${t.skill_hash.slice(0,12)}...`)))}T(e,`before_tool_call`,s,E(e));let c;try{let t=nt(tt(await K(e,`before_tool_call`,s)));e.logger.info(x(`[before_tool_call] detect effect applied: ${JSON.stringify(t.returnValue)}`)),c=t.returnValue}catch(t){e.logger.error(x(`[before_tool_call] detect request failed: ${t instanceof Error?t.message:String(t)}`))}let l=Date.now()-i;return e.logger.info(x(`[before_tool_call] total_elapsed=${l}ms`)),c}function at(e,t){e.on(`llm_input`,async(n,r)=>{await rt(e,t,`llm_input`,n,r)}),e.on(`llm_output`,async(n,r)=>{await rt(e,t,`llm_output`,n,r)}),e.on(`after_tool_call`,async(n,r)=>{await rt(e,t,`after_tool_call`,n,r)}),e.on(`before_tool_call`,async(n,r)=>it(e,t,n,r))}function Q(e){return typeof e==`object`&&!!e}function ot(e,t){if(!Q(e))return null;let n=e[t];return Q(n)?n:null}function st(e){let t=ot(ot(e,`models`),`providers`);return t?Object.entries(t).map(([e,t])=>{if(!Q(t))return null;let n=t.baseUrl??t.baseURL;return typeof n==`string`&&n?{providerId:e,baseUrl:n}:null}).filter(e=>e!==null):[]}function ct(e,t){for(let n of t)if(e.startsWith(n.baseUrl))return n;return null}function lt(e){return typeof e==`string`?e:e instanceof URLSearchParams?e.toString():e instanceof Uint8Array?new TextDecoder().decode(e):ArrayBuffer.isView(e)?new TextDecoder().decode(new Uint8Array(e.buffer,e.byteOffset,e.byteLength)):e instanceof ArrayBuffer?new TextDecoder().decode(new Uint8Array(e)):null}function ut(e){let t=lt(e);if(!t)return null;try{let e=JSON.parse(t);return Q(e)?e:null}catch{return null}}function dt(e){return Q(e)?Array.isArray(e.messages)||typeof e.prompt==`string`||typeof e.input==`string`:!1}function ft(e){return typeof e==`string`&&e.includes(`/open-apis/security_plugin/v1/openclaw_plugin`)}function pt(e){return typeof e==`string`&&e.includes(`text/event-stream`)}function mt(e,t){let n=e.split(/\r?\n/).filter(e=>e.startsWith(`data:`)).map(e=>e.slice(5).trim()).filter(e=>e.length>0&&e!==`[DONE]`),r=Math.max(1,Number(t)||1);if(r===1)return n;let i=[];for(let e=0;e<n.length;e+=r)i.push(n.slice(e,e+r).join(`
2
+ `));return i}function ht(e,t){return t?`${String(e).split(/\r?\n/).map(e=>`data: ${e}`).join(`
3
+ `)}\n\n`:e}function gt(e,t,n){let r=new Headers(e.headers);return r.delete(`content-length`),n?r.set(`content-type`,`text/event-stream; charset=utf-8`):r.get(`content-type`)||r.set(`content-type`,`text/plain; charset=utf-8`),new Response(ht(t,n),{status:e.status,statusText:e.statusText,headers:r})}async function _t(e,t,n){let r=await t.clone().text(),i=pt(t.headers.get(`content-type`)??``);return{url:e,isSse:i,chunks:i?mt(r,n):[],originResp:i?``:r}}function vt(...e){return e.find(e=>typeof e==`string`)??``}function yt(e){return typeof e==`string`?e.toUpperCase():`PERMIT`}function bt(e){return{effect:yt(e?.effect),denyOutput:vt(e?.deny_output),rewriteOutput:vt(e?.rewrite_output)}}function xt(e){let t=yt(e.effect);return t===`DENY`&&e.denyOutput?{action:`deny`,output:e.denyOutput,effect:t}:t===`REWRITE`&&e.rewriteOutput?{action:`rewrite`,output:e.rewriteOutput,effect:t}:{action:`permit`,output:``,effect:t}}function St(e){let t=yt(e.effect);return t===`DENY`&&e.denyOutput?{action:`deny`,output:e.denyOutput,effect:t}:t===`REWRITE`&&e.rewriteOutput?{action:`rewrite`,output:e.rewriteOutput,effect:t}:{action:`permit`,output:``,effect:t}}function Ct(e,t,n){e.logger.info(x(`[${t}] ${w(n,E(e))}`))}function wt(){let e=globalThis.crypto;return e?.randomUUID?e.randomUUID():`${Date.now()}-${Math.random().toString(16).slice(2)}`}function Tt(e){let t=String(e);function n(e){if(typeof e!=`string`)return JSON.stringify(e);try{let t=JSON.parse(e);return typeof t==`string`?t:JSON.stringify(t)}catch{return e}}try{let e=JSON.parse(t);if(Array.isArray(e))return e.map(e=>n(e))}catch{}return t.split(/\r?\n/)}function Et(e,t){let n=new Headers(e.headers);n.delete(`content-length`),n.set(`content-type`,`text/event-stream; charset=utf-8`);let r=`${Tt(t).map(e=>`data: ${e}\n\n`).join(``)}`;return new Response(r,{status:e.status,statusText:e.statusText,headers:n})}function Dt(e){return(e.headers.get(`content-type`)??``).includes(`text/event-stream`)}async function Ot(e,t,n,r,i,a){let o=r.body;if(!o)return r;let s=Math.max(1,Number(await i.getSseChunkSize())||1),c=o.getReader(),l=new TextDecoder,u=new TextEncoder,d=``,f=[],p=[];async function m(r){let i=X(`after_llm_fetch`,null,null,{request_id:n,url:t,isSse:!0,chunks:r,originResp:``});return Ct(e,`after_llm_fetch`,i),St(bt(await K(e,`after_llm_fetch`,i)))}function h(e){f.length!==0&&(e.enqueue(u.encode(f.join(``))),f=[])}let g=new ReadableStream({async start(t){try{for(;;){let{value:i,done:o}=await c.read();if(o)break;for(d+=l.decode(i,{stream:!0});;){let i=d.indexOf(`
4
+
5
+ `),o=2;if(i<0&&(i=d.indexOf(`\r
6
+ \r
7
+ `),o=4),i<0)break;let l=d.slice(0,i);d=d.slice(i+o);let g=`${l}\n\n`;f.push(g);let _=l.split(/\r?\n/);for(let e of _){if(!e.startsWith(`data:`))continue;let t=e.slice(5).trim();!t||t===`[DONE]`||p.push(t)}if(p.length>=s){try{let i=await m(p);if((i.action===`deny`||i.action===`rewrite`)&&i.output){f=[],p=[];let o=await Et(r,i.output).text();t.enqueue(u.encode(o));try{await c.cancel()}catch{}t.close(),e.logger.info(x(`[llm_fetch] total_elapsed=${Date.now()-a}ms request_id=${n}`));return}}catch(t){e.logger.error(x(`[after_llm_fetch] detect failed; passthrough buffered events: ${t instanceof Error?t.message:String(t)}`))}h(t),p=[]}}}if(d&&=(f.push(d),``),p.length>0)try{let i=await m(p);if((i.action===`deny`||i.action===`rewrite`)&&i.output){f=[],p=[];let o=await Et(r,i.output).text();t.enqueue(u.encode(o));try{await c.cancel()}catch{}t.close(),e.logger.info(x(`[llm_fetch] total_elapsed=${Date.now()-a}ms request_id=${n}`));return}}catch(t){e.logger.error(x(`[after_llm_fetch] detect failed; passthrough buffered events: ${t instanceof Error?t.message:String(t)}`))}h(t),t.close(),e.logger.info(x(`[llm_fetch] total_elapsed=${Date.now()-a}ms request_id=${n}`))}catch(e){try{await c.cancel()}catch{}t.error(e)}}}),_=new Headers(r.headers);return _.delete(`content-length`),_.set(`content-type`,`text/event-stream; charset=utf-8`),new Response(g,{status:r.status,statusText:r.statusText,headers:_})}function kt(e,t){let n=globalThis.fetch;if(typeof n!=`function`){e.logger.warn(x(`[before_llm_fetch] global.fetch is unavailable; skipping interceptor`));return}globalThis.__openclawGuardianOriginalFetch||(globalThis.__openclawGuardianOriginalFetch=n,globalThis.fetch=async function(r,i){let a=[r,i],o=r?.toString?.()??``,s=i??{},c=()=>n.call(globalThis,a[0],a[1]);if(a[1]||=s,ft(o))return c();let l,u=null;try{let r=ct(o,st(e.config));if(!r)return e.logger.info(x(`[before_llm_fetch] pass-through: no provider baseUrl match for url=${o}`)),c();e.logger.info(x(`[before_llm_fetch] matched provider=${r.providerId} baseUrl=${r.baseUrl} url=${o}`));let i=ut(s.body);if(!dt(i))return e.logger.info(x(`[before_llm_fetch] pass-through: matched provider=${r.providerId} but request body is not a supported LLM payload shape`)),c();let d={request_id:wt(),url:o,rawBody:lt(s.body)??``,jsonBody:i},f=String(d.request_id);if(u=Date.now(),await t.isHookEnabled(`before_llm_fetch`)){let t=X(`before_llm_fetch`,null,null,d);Ct(e,`before_llm_fetch`,t);let n=bt(await K(e,`before_llm_fetch`,t)),i=xt(n);(i.action===`deny`||i.action===`rewrite`)&&i.output?(s.body=String(i.output),a[1]=s,e.logger.info(x(`[before_llm_fetch] detect effect applied: ${i.action} (use output as new body) provider=${r.providerId}`))):e.logger.info(x(`[before_llm_fetch] detect effect passthrough: effect=${n.effect} provider=${r.providerId}`))}else e.logger.info(x(`[before_llm_fetch] detect skipped: hook disabled by runtime config`));if(l=await n.apply(this,a),!await t.isHookEnabled(`after_llm_fetch`))return u!==null&&e.logger.info(x(`[llm_fetch] total_elapsed=${Date.now()-u}ms request_id=${f}`)),l;if(Dt(l))return await Ot(e,o,f,l,t,u??Date.now());let p=await _t(o,l,await t.getSseChunkSize()),m=X(`after_llm_fetch`,null,null,{...p,request_id:f});Ct(e,`after_llm_fetch`,m);let h=bt(await K(e,`after_llm_fetch`,m)),g=St(h);return(g.action===`deny`||g.action===`rewrite`)&&g.output?(e.logger.info(x(`[after_llm_fetch] detect effect applied: ${g.action} provider=${r.providerId}`)),u!==null&&e.logger.info(x(`[llm_fetch] total_elapsed=${Date.now()-u}ms url=${o}`)),gt(l,g.output,p.isSse)):(e.logger.info(x(`[after_llm_fetch] detect effect passthrough: effect=${h.effect} provider=${r.providerId}`)),u!==null&&e.logger.info(x(`[llm_fetch] total_elapsed=${Date.now()-u}ms request_id=${f}`)),l)}catch(t){return e.logger.error(x(`[llm_fetch] detect request failed, allowing original fetch flow: ${t instanceof Error?t.message:String(t)}`)),l?(u!==null&&e.logger.info(x(`[llm_fetch] total_elapsed=${Date.now()-u}ms request_id=<unknown>`)),l):c()}})}function $(e){return e.replace(/\0/g,``)}function At(e,t){let n=s(),r=new Set,a=e.config??{},c=a?.skills?.load?.extraDirs;if(Array.isArray(c))for(let e of c)typeof e==`string`&&e.trim()&&r.add(o($(e.trim())));r.add(i(n,`.openclaw`,`skills`)),r.add(i(n,`.agents`,`skills`));let l=new Set,u=a?.agents?.defaults?.workspace;if(typeof u==`string`&&u.trim()&&l.add(o($(u.trim()))),Array.isArray(a?.agents?.list))for(let e of a.agents.list)typeof e?.workspace==`string`&&e.workspace.trim()&&l.add(o($(e.workspace.trim())));for(let e of l)r.add(i(e,`skills`)),r.add(i(e,`.agents`,`skills`));let d,f=e.pluginConfig;if(typeof f?.openClawDir==`string`&&f.openClawDir.trim())d=o($(f.openClawDir.trim()));else try{let t=e.runtime?.state?.resolveStateDir?.();typeof t==`string`&&t.trim()&&(d=t)}catch{}d&&(r.add(i(d,`skills`)),r.add(i(d,`.agents`,`skills`)));let p=a?.plugins;if(p?.installs&&typeof p.installs==`object`){for(let e of Object.values(p.installs))if(typeof e?.installPath==`string`&&e.installPath.trim()){let t=i($(e.installPath.trim()),`skills`);r.add(o(t))}}let m=t??i(n,`.openclaw`,`workspace`);r.add(i(m,`.agents`,`skills`)),r.add(i(m,`skills`));let h=Array.from(r),g=h.filter(e=>v(e));return e.logger.info(x(`[skill-scan] resolved ${h.length} skill path(s), ${g.length} exist`)),g}async function jt(e){let t;try{t=await f(e)}catch{return[]}let n=[];for(let r of t){let t=i(e,r),a=await l(t);a.isSymbolicLink()||(a.isDirectory()?n.push(...await jt(t)):a.isFile()&&n.push(t))}return n}const Mt=10*1024*1024;async function Nt(e){let t=await jt(e);if(t.length===0)return _(`sha256`).update(`empty-skill-dir`).digest(`hex`);let r=[],i=0;for(let a of t){let t=await d(a);if(i+=t.length,i>Mt)throw Error(`computeCanonicalHash: skill directory exceeds ${Mt} byte limit: ${e}`);let o=n(a),s=_(`sha256`).update(t).digest(`hex`);r.push(`${o}:${s}`)}return r.sort(),_(`sha256`).update(r.join(`
8
+ `)).digest(`hex`)}function Pt(e){let t=/^name:\s*(.+)$/m.exec(e);return t?t[1].trim():null}function Ft(e){let t=/^---\n([\s\S]*?)\n---/.exec(e);return t?t[1]:``}async function It(e,t){let n;try{n=await f(t,{withFileTypes:!0,encoding:`utf8`})}catch{return[]}let r=[];for(let a of n){if(!a.isDirectory())continue;let n=i(t,a.name),o=i(n,`SKILL.md`);try{let e=Pt(Ft(await d(o,`utf-8`)))||a.name,t=await Nt(n);r.push({dirName:a.name,dirPath:n,filePath:o,skillName:e,skillHash:t})}catch(t){t instanceof Error&&t.message.includes(`byte limit`)&&e.logger.warn(x(`[skill-scan] skipping skill '${a.name}': ${t.message}`))}}return r}async function Lt(e,t){let n=At(e,t);e.logger.info(x(`[skill-scan] scanning ${n.length} skill location(s)`));let r=new Map;for(let t of n){let n=await It(e,t);n.length>0&&e.logger.info(x(`[skill-scan] ${t}: ${n.length} skill(s)`));for(let e of n)r.set(e.skillName,e)}let i=Array.from(r.values());return e.logger.info(x(`[skill-scan] scan complete: ${i.length} unique skill(s) across all locations`)),i}function Rt(e,t,n,r,i){e[t]={...n,skill_name:r.skill_name,skill_hash:r.skill_hash,is_detected:i,last_detect_ts:Date.now()}}async function zt(e,t){e.logger.info(x(`[skill-scan] starting skill scan`));let n=Date.now(),r=Ae(t),i=await Lt(e,t);if(i.length===0){e.logger.info(x(`[skill-scan] no skills found, skipping detection`));return}let a=Be(await Re(e,r),i);e.logger.info(x(`[skill-scan] merged state: ${Object.keys(a).length} skill(s)`));let o=new Map(i.map(e=>[e.dirName,e])),s=[];for(let[t,n]of Object.entries(a))if(o.get(t)){if(Pe(n)){e.logger.warn(x(`[skill-scan] ${n.skill_name}: skill_hash missing, skipping`));continue}n.is_detected||(e.logger.info(x(`[skill-scan] ${n.skill_name}: not detected, queuing`)),s.push({dirName:t,entry:n,entity:{skill_name:n.skill_name,skill_hash:n.skill_hash}}))}if(e.logger.info(x(`[skill-scan] ${s.length} skill(s) need detection`)),s.length===0){await ze(e,a,r),e.logger.info(x(`[skill-scan] skill scan complete (nothing to detect) total_elapsed=${Date.now()-n}ms scanned=${i.length}`));return}let c={};for(let{dirName:e}of s)c[e]=a[e];let l={},u=!1;try{l=await Ge(e,c),e.logger.info(x(`[skill-scan] batch_check result: ${JSON.stringify(l)}`))}catch(t){e.logger.error(x(`[skill-scan] batch_check_detection failed: ${t instanceof Error?t.message:String(t)}`)),u=!0}for(let{dirName:t,entry:n,entity:r}of s)l[Z(r.skill_hash)]===!0&&(Rt(a,t,n,r,!0),e.logger.info(x(`[skill-scan] ${r.skill_name}: already known by server (batch_check)`)));if(u){e.logger.warn(x(`[skill-scan] skipping individual detect calls: batch_check_detection was unreachable`)),await ze(e,a,r),e.logger.info(x(`[skill-scan] skill scan complete (partial — batch_check failed) total_elapsed=${Date.now()-n}ms scanned=${i.length}`));return}for(let{dirName:t,entry:n,entity:r}of s){if(l[r.skill_hash]===!0)continue;let n=o.get(t);if(n)try{let i=await Ye(e,n.dirPath,r);Rt(a,t,a[t],r,i),e.logger.info(x(`[skill-scan] detect result for ${r.skill_name}: is_detected=${i}`))}catch(n){e.logger.error(x(`[skill-scan] detect failed for ${r.skill_name}: ${n instanceof Error?n.message:String(n)}`)),Rt(a,t,a[t],r,!1)}}await ze(e,a,r),e.logger.info(x(`[skill-scan] skill scan complete total_elapsed=${Date.now()-n}ms scanned=${i.length} to_detect=${s.length}`))}async function Bt(e,t,n,r){e.logger.info(x(`[gateway_start] gateway ready on port=${n.port} ctx=${JSON.stringify(r)}`))}function Vt(e,t){let n=null,r=!1,i=!1,a;async function o(){if(i)return;let s=await t.getSkillScanIntervalMs();e.logger.info(x(`[skill-scan-service] next scan in ${s/1e3}s`)),n=setTimeout(async()=>{if(!i){if(!await t.isSkillDetectEnabled()){e.logger.info(x(`[skill-scan-service] periodic scan skipped: skill_detect disabled`)),await o();return}if(r){e.logger.info(x(`[skill-scan-service] periodic scan skipped: previous scan still running`)),await o();return}e.logger.info(x(`[skill-scan-service] periodic scan triggered`)),r=!0,zt(e,a).catch(t=>{e.logger.error(x(`[skill-scan-service] periodic scan failed: ${t instanceof Error?t.message:String(t)}`))}).finally(()=>{r=!1,o()})}},s),n.unref()}return{id:`openclaw-guardian-skill-scan`,async start(n){a=n.workspaceDir,e.logger.info(x(`[skill-scan-service] starting with workspaceDir=${a??`(default)`}`)),i=!1,await t.isSkillDetectEnabled()&&(e.logger.info(x(`[skill-scan-service] running initial skill scan`)),r=!0,zt(e,a).catch(t=>{e.logger.error(x(`[skill-scan-service] initial scan failed: ${t instanceof Error?t.message:String(t)}`))}).finally(()=>{r=!1})),await o()},async stop(t){i=!0,n&&=(clearTimeout(n),null),e.logger.info(x(`[skill-scan-service] stopped`))}}}function Ht(e,t){e.on(`gateway_start`,async(n,r)=>{await Bt(e,t,n,r)}),e.registerService(Vt(e,t))}const Ut=[...[`before_tool_call`,`after_tool_call`,`before_llm_fetch`,`after_llm_fetch`,`llm_input`,`llm_output`],`skill_detect`];function Wt(){return Object.fromEntries(Ut.map(e=>[e,!1]))}function Gt(e){let t=Wt(),n=e&&typeof e==`object`?e:{};for(let e of Ut)t[e]=!!n[e];return t}function Kt(e){return typeof e==`number`&&Number.isFinite(e)&&e>0?Math.floor(e):1}function qt(e){return typeof e==`number`&&Number.isFinite(e)&&e>0?Math.floor(e):600}function Jt(e){return typeof e==`number`&&Number.isFinite(e)&&e>0?Math.floor(e):900}function Yt(e){return{enabled:!1,hook_toggles:Wt(),sse_chunk_size:1,skill_scan_duration_second:900,query_duration_second:600,reason:e}}function Xt(e){let t=e&&typeof e==`object`?e:{},n=t.enabled===void 0?!0:!!t.enabled;return{enabled:n,hook_toggles:n?Gt(t.hook_toggles):Wt(),sse_chunk_size:Kt(t.sse_chunk_size),skill_scan_duration_second:Jt(t.skill_scan_duration_second),query_duration_second:qt(t.query_duration_second),reason:n?`remote`:`remote_disabled`}}function Zt(e){return st(e.config).map(e=>e.baseUrl)}async function Qt(e){let t={source:`miaoda`,base_url_list:Zt(e)};return Xt(await G(e,`runtime_config`,se(e),{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify(t)},H))}function $t(e){let t=Yt(`not_initialized`),n=!1,r=null,i=null;async function a(){let r=null;for(let i=1;i<=3;i+=1)try{let r=await Qt(e);return t=r,n=!0,e.logger.debug?.(x(`[runtime_config] initial config loaded on attempt=${i} enabled_hooks=${JSON.stringify(r.hook_toggles)}`)),t}catch(t){r=t;let n=V(t);if(e.logger.error(x(`[runtime_config] initial config load failed attempt=${i} errorCode=${n}: ${t instanceof Error?t.message:String(t)}`)),n===`SERVER`||n===`PARSE`)break}return t=Yt(`initial_fetch_failed`),e.logger.warn(x(`[runtime_config] initial config unavailable after 3 attempts; detection disabled by default${r?`: ${r instanceof Error?r.message:String(r)}`:``}`)),t}async function o(){try{let r=await Qt(e);return t=r,n=!0,e.logger.debug?.(x(`[runtime_config] config refreshed enabled_hooks=${JSON.stringify(r.hook_toggles)} sse_chunk_size=${r.sse_chunk_size}`)),t}catch(r){let i=V(r);return n?(e.logger.error(x(`[runtime_config] refresh failed (errorCode=${i}); keeping previous config: ${r instanceof Error?r.message:String(r)}`)),t):(e.logger.error(x(`[runtime_config] refresh failed before first successful load (errorCode=${i}): ${r instanceof Error?r.message:String(r)}`)),t=Yt(`refresh_failed_${i}`),t)}}function s(){r&&clearTimeout(r);let n=t.query_duration_second*1e3;e.logger.debug?.(x(`[runtime_config] next config refresh in ${t.query_duration_second}s`)),r=setTimeout(()=>{e.logger.debug?.(x(`[runtime_config] refresh loop fired`)),o().catch(t=>{e.logger.error(x(`[runtime_config] refresh loop failed: ${t instanceof Error?t.message:String(t)}`))}).finally(()=>{s()})},n),r.unref()}function c(){return i||=a().catch(n=>(e.logger.error(x(`[runtime_config] initialize failed: ${n instanceof Error?n.message:String(n)}`)),t)).finally(()=>{s()}),i}return{async isHookEnabled(e){return await c(),!!(t?.enabled&&t?.hook_toggles?.[e])},async isSkillDetectEnabled(){return await c(),!!(t?.enabled&&t?.hook_toggles?.skill_detect)},async getSseChunkSize(){return await c(),Kt(t?.sse_chunk_size)},async getSkillScanIntervalMs(){return await c(),Jt(t?.skill_scan_duration_second)*1e3}}}var en=t({id:`openclaw-guardian-plugin`,name:`OpenClaw Guardian Plugin`,description:`Reports OpenClaw hook payloads to the trust_layer detect service with remote hook toggles.`,register(e){if(!N(e)){e.logger.debug?.(x(`[plugin] missing Feishu appId/appSecret from api.config.channels.feishu or pluginConfig; skip all hooks and reporting`));return}let t=$t(e);kt(e,t),at(e,t),Ht(e,t)}});export{en as default};
@@ -2,7 +2,7 @@
2
2
  "id": "openclaw-guardian-plugin",
3
3
  "name": "OpenClaw Guardian Plugin",
4
4
  "description": "Reports OpenClaw hook payloads to security_plugin config and detect endpoints.",
5
- "version": "2026.4.10",
5
+ "version": "2026.4.19",
6
6
  "uiHints": {
7
7
  "appId": {
8
8
  "label": "Feishu App ID",
package/package.json CHANGED
@@ -1,30 +1,30 @@
1
1
  {
2
2
  "name": "@lark-tns/openclaw-guardian-plugin",
3
- "version": "2026.4.15",
3
+ "version": "2026.4.19",
4
4
  "type": "module",
5
5
  "description": "OpenClaw plugin that reports hook payloads to the security_plugin detect service with remote hook toggles.",
6
6
  "keywords": ["openclaw", "plugin", "security", "guardian"],
7
7
  "files": [
8
- "src/**/*.ts",
8
+ "dist",
9
9
  "openclaw.plugin.json",
10
10
  "README.md"
11
11
  ],
12
12
  "scripts": {
13
+ "build": "tsdown",
13
14
  "typecheck": "tsc --noEmit",
14
- "pack": "npm pack",
15
+ "prepublishOnly": "npm run build",
16
+ "pack": "npm run build && npm pack",
15
17
  "upload": "node scripts/uploadManifest.mjs"
16
18
  },
17
19
  "openclaw": {
18
20
  "extensions": [
19
- "./src/index.ts"
20
- ],
21
- "installDependencies": true
21
+ "./dist/index.js"
22
+ ]
22
23
  },
23
24
  "devDependencies": {
24
25
  "@types/yazl": "^3.3.0",
25
- "typescript": "^5.8.3"
26
- },
27
- "dependencies": {
26
+ "tsdown": "^0.12.5",
27
+ "typescript": "^5.8.3",
28
28
  "yazl": "^3.3.1"
29
29
  },
30
30
  "peerDependencies": {
@@ -34,5 +34,8 @@
34
34
  "openclaw": {
35
35
  "optional": false
36
36
  }
37
+ },
38
+ "publishConfig": {
39
+ "access": "public"
37
40
  }
38
41
  }
@@ -1,27 +0,0 @@
1
- import { getHostname } from './hostname';
2
-
3
- export type CommonPayloadFields = {
4
- hostname: string;
5
- };
6
-
7
- export function buildCommonPayloadFields(): CommonPayloadFields {
8
- return {
9
- hostname: getHostname(),
10
- };
11
- }
12
-
13
- export function mergeCommonPayloadFields(payload: unknown): unknown {
14
- if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
15
- return payload;
16
- }
17
-
18
- const record = payload as Record<string, unknown>;
19
- if (Object.prototype.hasOwnProperty.call(record, 'hostname')) {
20
- return payload;
21
- }
22
-
23
- return {
24
- ...record,
25
- ...buildCommonPayloadFields(),
26
- };
27
- }
@@ -1,16 +0,0 @@
1
- import { DEFAULT_TIMEOUT_MS, fetchSecurityPluginApi, getDetectUrl, createDetectRequestBody } from './http-client';
2
- import { mergeCommonPayloadFields } from './common-payload';
3
- import type { OpenClawPluginApi } from 'openclaw/plugin-sdk';
4
-
5
- export async function detectHookPayload(api: OpenClawPluginApi, hookName: string, payload: unknown) {
6
- const source = 'miaoda'; //TODO: Mock for now, should get from config later
7
- const body = createDetectRequestBody(hookName, mergeCommonPayloadFields(payload), source);
8
-
9
- return fetchSecurityPluginApi(api, hookName, getDetectUrl(api), {
10
- method: 'POST',
11
- headers: {
12
- 'content-type': 'application/json',
13
- },
14
- body: JSON.stringify(body),
15
- }, DEFAULT_TIMEOUT_MS);
16
- }
@@ -1,269 +0,0 @@
1
- import { basename, dirname } from 'node:path';
2
- import type {
3
- PluginHookAfterToolCallEvent,
4
- PluginHookAgentContext,
5
- PluginHookBeforeToolCallEvent,
6
- PluginHookBeforeToolCallResult,
7
- PluginHookLlmInputEvent,
8
- PluginHookLlmOutputEvent,
9
- PluginHookToolContext,
10
- } from 'openclaw/plugin-sdk/plugin-runtime';
11
- import type { OpenClawPluginApi } from 'openclaw/plugin-sdk';
12
- import { detectHookPayload } from '../detect-service';
13
- import { buildHookPayload } from '../hook-payloads';
14
- import { logHookPayloadWithEnv, withLogPrefix } from '../logger';
15
- import { getPluginEnv } from '../plugin-env';
16
- import type { RuntimeConfigStore } from '../runtime-config';
17
- import { loadSkillState, resolveStoragePath } from '../skill-scan/skill-storage';
18
-
19
- type UnknownRecord = Record<string, unknown>;
20
-
21
- /**
22
- * Returns the SKILL.md file path if this tool call is a skill read:
23
- * tool_name === "read" and tool_args.path ends with "SKILL.md".
24
- */
25
- function extractSkillFilePath(payload: UnknownRecord): string | null {
26
- if (payload.tool_name !== 'read') return null;
27
- const args = payload.tool_args;
28
- if (!args || typeof args !== 'object' || Array.isArray(args)) return null;
29
- const values = Object.values(args as UnknownRecord);
30
- return (values.find((v): v is string => typeof v === 'string' && v.endsWith('SKILL.md')) ?? null);
31
- }
32
-
33
- async function resolveSkillMeta(
34
- api: OpenClawPluginApi,
35
- skillFilePath: string,
36
- agentId?: string,
37
- ): Promise<{ skill_name: string; skill_hash: string } | null> {
38
- // dirName is the cache key in skill-detect-state.json
39
- const dirName = basename(dirname(skillFilePath));
40
- try {
41
- let storagePath: string | undefined;
42
- if (agentId) {
43
- const workspaceDir = api.runtime.agent.resolveAgentWorkspaceDir(api.config, agentId);
44
- storagePath = resolveStoragePath(workspaceDir);
45
- }
46
- const state = await loadSkillState(api, storagePath);
47
- const entry = state[dirName];
48
- if (!entry || !entry.zip_hash) return null;
49
- return { skill_name: entry.skill_name, skill_hash: entry.zip_hash };
50
- } catch {
51
- return null;
52
- }
53
- }
54
-
55
- type DetectResponseLike = {
56
- effect?: unknown;
57
- deny_output?: unknown;
58
- rewrite_output?: unknown;
59
- };
60
-
61
- type DetectDecision = {
62
- effect: string;
63
- denyOutput: string;
64
- rewriteOutput: string;
65
- };
66
-
67
- type ReportOnlyHookName = 'llm_input' | 'llm_output' | 'after_tool_call';
68
-
69
- type ReportOnlyHookEventMap = {
70
- llm_input: PluginHookLlmInputEvent;
71
- llm_output: PluginHookLlmOutputEvent;
72
- after_tool_call: PluginHookAfterToolCallEvent;
73
- };
74
-
75
- type ReportOnlyHookContextMap = {
76
- llm_input: PluginHookAgentContext;
77
- llm_output: PluginHookAgentContext;
78
- after_tool_call: PluginHookToolContext;
79
- };
80
-
81
- function firstString(...values: unknown[]): string {
82
- return values.find((value) => typeof value === 'string') ?? '';
83
- }
84
-
85
- function normalizeEffect(effect: unknown): string {
86
- if (typeof effect !== 'string') {
87
- return 'PERMIT';
88
- }
89
-
90
- return effect.toUpperCase();
91
- }
92
-
93
- function parseRewriteParams(text: string): UnknownRecord | undefined {
94
- if (typeof text !== 'string' || text.length === 0) {
95
- return undefined;
96
- }
97
-
98
- try {
99
- const parsed = JSON.parse(text);
100
- return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
101
- ? (parsed as UnknownRecord)
102
- : undefined;
103
- } catch {
104
- return undefined;
105
- }
106
- }
107
-
108
- function parseDetectDecision(response: DetectResponseLike | null | undefined): DetectDecision {
109
- return {
110
- effect: normalizeEffect(response?.effect),
111
- denyOutput: firstString(response?.deny_output),
112
- rewriteOutput: firstString(response?.rewrite_output),
113
- };
114
- }
115
-
116
- function applyBeforeToolCallDecision(decision: DetectDecision) {
117
- const effect = normalizeEffect(decision.effect);
118
-
119
- if (effect === 'DENY') {
120
- if (!decision.denyOutput) {
121
- return {
122
- action: 'permit',
123
- returnValue: undefined,
124
- };
125
- }
126
-
127
- return {
128
- action: 'deny',
129
- output: decision.denyOutput,
130
- returnValue: {
131
- block: true,
132
- blockReason: decision.denyOutput || 'Tool call blocked by detect service',
133
- },
134
- };
135
- }
136
-
137
- if (effect === 'REWRITE') {
138
- if (!decision.rewriteOutput) {
139
- return {
140
- action: 'permit',
141
- returnValue: undefined,
142
- };
143
- }
144
-
145
- const rewrittenParams = parseRewriteParams(decision.rewriteOutput);
146
- if (!rewrittenParams) {
147
- return {
148
- action: 'rewrite_invalid',
149
- returnValue: undefined,
150
- };
151
- }
152
-
153
- return {
154
- action: 'rewrite',
155
- returnValue: {
156
- params: rewrittenParams,
157
- },
158
- };
159
- }
160
-
161
- return {
162
- action: 'permit',
163
- returnValue: undefined,
164
- };
165
- }
166
-
167
- async function handleReportOnlyHook<K extends ReportOnlyHookName>(
168
- api: OpenClawPluginApi,
169
- configStore: RuntimeConfigStore,
170
- hookName: K,
171
- event: ReportOnlyHookEventMap[K],
172
- ctx: ReportOnlyHookContextMap[K],
173
- ): Promise<void> {
174
- if (!(await configStore.isHookEnabled(hookName))) {
175
- api.logger.info(withLogPrefix(`[${hookName}] detect skipped: hook disabled by runtime config`));
176
- return;
177
- }
178
-
179
- const totalStart = Date.now();
180
- const payload = buildHookPayload(hookName, event, ctx);
181
- logHookPayloadWithEnv(api, hookName, payload, getPluginEnv(api));
182
-
183
- try {
184
- const detectResponse = await detectHookPayload(api, hookName, payload);
185
- const decision = parseDetectDecision(detectResponse);
186
- api.logger.info(
187
- withLogPrefix(`[${hookName}] detect effect observed: effect=${decision.effect} (report-only hook)`),
188
- );
189
- } catch (error) {
190
- api.logger.error(
191
- withLogPrefix(
192
- `[${hookName}] detect request failed: ${error instanceof Error ? error.message : String(error)}`,
193
- ),
194
- );
195
- } finally {
196
- const totalElapsed = Date.now() - totalStart;
197
- api.logger.info(withLogPrefix(`[${hookName}] total_elapsed=${totalElapsed}ms`));
198
- }
199
- }
200
-
201
- async function handleBeforeToolCall(
202
- api: OpenClawPluginApi,
203
- configStore: RuntimeConfigStore,
204
- event: PluginHookBeforeToolCallEvent,
205
- ctx: PluginHookToolContext,
206
- ): Promise<PluginHookBeforeToolCallResult> {
207
- const totalStart = Date.now();
208
- if (!(await configStore.isHookEnabled('before_tool_call'))) {
209
- api.logger.info(withLogPrefix('[before_tool_call] detect skipped: hook disabled by runtime config'));
210
- return undefined;
211
- }
212
-
213
- const payload = buildHookPayload('before_tool_call', event, ctx);
214
-
215
- // Enrich payload with skill metadata if this is a skill read
216
- const skillFilePath = extractSkillFilePath(payload as UnknownRecord);
217
- let enrichedPayload: UnknownRecord = payload as UnknownRecord;
218
- if (skillFilePath) {
219
- const skillMeta = await resolveSkillMeta(api, skillFilePath, ctx.agentId);
220
- if (skillMeta) {
221
- enrichedPayload = { ...enrichedPayload, ...skillMeta };
222
- api.logger.info(
223
- withLogPrefix(
224
- `[before_tool_call] skill read detected: skill=${skillMeta.skill_name} hash=${skillMeta.skill_hash.slice(0, 12)}...`,
225
- ),
226
- );
227
- }
228
- }
229
-
230
- logHookPayloadWithEnv(api, 'before_tool_call', enrichedPayload, getPluginEnv(api));
231
-
232
- let hookReturnValue: PluginHookBeforeToolCallResult = undefined;
233
-
234
- try {
235
- const detectResponse = await detectHookPayload(api, 'before_tool_call', enrichedPayload);
236
- const decision = parseDetectDecision(detectResponse);
237
- const outcome = applyBeforeToolCallDecision(decision);
238
- api.logger.info(withLogPrefix(`[before_tool_call] detect effect applied: ${JSON.stringify(outcome.returnValue)}`));
239
- hookReturnValue = outcome.returnValue;
240
- } catch (error) {
241
- api.logger.error(
242
- withLogPrefix(
243
- `[before_tool_call] detect request failed: ${error instanceof Error ? error.message : String(error)}`,
244
- ),
245
- );
246
- }
247
-
248
- const totalElapsed = Date.now() - totalStart;
249
- api.logger.info(withLogPrefix(`[before_tool_call] total_elapsed=${totalElapsed}ms`));
250
- return hookReturnValue;
251
- }
252
-
253
- export function registerDetectHooks(api: OpenClawPluginApi, configStore: RuntimeConfigStore): void {
254
- api.on('llm_input', async (event, ctx) => {
255
- await handleReportOnlyHook(api, configStore, 'llm_input', event, ctx);
256
- });
257
-
258
- api.on('llm_output', async (event, ctx) => {
259
- await handleReportOnlyHook(api, configStore, 'llm_output', event, ctx);
260
- });
261
-
262
- api.on('after_tool_call', async (event, ctx) => {
263
- await handleReportOnlyHook(api, configStore, 'after_tool_call', event, ctx);
264
- });
265
-
266
- api.on('before_tool_call', async (event, ctx) => {
267
- return handleBeforeToolCall(api, configStore, event, ctx);
268
- });
269
- }
@@ -1,45 +0,0 @@
1
- import type { ContextLike, FetchMeta, LlmEventLike, ToolEventLike } from './shared';
2
- import { safeObject } from './shared';
3
- import { buildAfterLlmFetchPayload, buildBeforeLlmFetchPayload } from './llm-fetch-payloads';
4
- import { buildLlmInputPayload, buildLlmOutputPayload } from './llm-payloads';
5
- import { buildAfterToolCallPayload, buildBeforeToolCallPayload } from './tool-payloads';
6
-
7
- export type HookName =
8
- | 'before_tool_call'
9
- | 'after_tool_call'
10
- | 'before_llm_fetch'
11
- | 'after_llm_fetch'
12
- | 'llm_input'
13
- | 'llm_output';
14
-
15
- export { buildAfterLlmFetchPayload, buildBeforeLlmFetchPayload } from './llm-fetch-payloads';
16
- export { buildLlmInputPayload, buildLlmOutputPayload } from './llm-payloads';
17
- export { buildAfterToolCallPayload, buildBeforeToolCallPayload } from './tool-payloads';
18
-
19
- export function buildHookPayload(
20
- hookName: HookName,
21
- event: unknown,
22
- ctx: unknown,
23
- extra?: unknown,
24
- ) {
25
- const toolEvent = event as ToolEventLike | null | undefined;
26
- const llmEvent = event as LlmEventLike | null | undefined;
27
- const safeContext = ctx as ContextLike | null | undefined;
28
-
29
- switch (hookName) {
30
- case 'before_tool_call':
31
- return buildBeforeToolCallPayload(toolEvent, safeContext);
32
- case 'after_tool_call':
33
- return buildAfterToolCallPayload(toolEvent, safeContext);
34
- case 'before_llm_fetch':
35
- return buildBeforeLlmFetchPayload(extra as FetchMeta | null | undefined);
36
- case 'after_llm_fetch':
37
- return buildAfterLlmFetchPayload(extra as FetchMeta | null | undefined);
38
- case 'llm_input':
39
- return buildLlmInputPayload(llmEvent, safeContext);
40
- case 'llm_output':
41
- return buildLlmOutputPayload(llmEvent, safeContext);
42
- default:
43
- return safeObject(extra);
44
- }
45
- }
@@ -1,26 +0,0 @@
1
- import type { FetchMeta } from './shared';
2
- import { firstDefined, parseUrlParts, safeArray, safeString } from './shared';
3
-
4
- export function buildBeforeLlmFetchPayload(meta: FetchMeta | null | undefined) {
5
- const { domain, path } = parseUrlParts(safeString(meta?.url));
6
-
7
- return {
8
- request_id: safeString(meta?.request_id),
9
- domain,
10
- path,
11
- origin_req: firstDefined(meta?.jsonBody, meta?.rawBody, ''),
12
- };
13
- }
14
-
15
- export function buildAfterLlmFetchPayload(meta: FetchMeta | null | undefined) {
16
- const { domain, path } = parseUrlParts(safeString(meta?.url));
17
-
18
- return {
19
- request_id: safeString(meta?.request_id),
20
- domain,
21
- path,
22
- is_sse: meta?.isSse ? 'true' : 'false',
23
- chunks: safeArray(meta?.chunks),
24
- origin_resp: meta?.isSse ? '' : safeString(firstDefined(meta?.originResp, '')),
25
- };
26
- }
@@ -1,33 +0,0 @@
1
- import type { ContextLike, LlmEventLike } from './shared';
2
- import { safeArray, safeString } from './shared';
3
-
4
- function createLlmBasePayload(event: LlmEventLike | null | undefined, ctx: ContextLike | null | undefined) {
5
- return {
6
- agent_name: safeString(ctx?.agentId),
7
- session_id: safeString(ctx?.sessionId),
8
- session_key: safeString(ctx?.sessionKey),
9
- channel_id: safeString(ctx?.channelId),
10
- message_provider: safeString(ctx?.messageProvider),
11
- trigger: safeString(ctx?.trigger),
12
- model: safeString(event?.model),
13
- provider: safeString(event?.provider),
14
- run_id: safeString(event?.runId),
15
- };
16
- }
17
-
18
- export function buildLlmInputPayload(event: LlmEventLike | null | undefined, ctx: ContextLike | null | undefined) {
19
- return {
20
- ...createLlmBasePayload(event, ctx),
21
- prompt: safeString(event?.prompt),
22
- system_prompt: safeString(event?.systemPrompt),
23
- history_messages: safeArray(event?.historyMessages),
24
- };
25
- }
26
-
27
- export function buildLlmOutputPayload(event: LlmEventLike | null | undefined, ctx: ContextLike | null | undefined) {
28
- return {
29
- ...createLlmBasePayload(event, ctx),
30
- assistant_texts: safeArray(event?.assistantTexts),
31
- last_assistant: safeString(JSON.stringify(event?.lastAssistant)),
32
- };
33
- }