@sleep2agi/agent-network 2.3.0-preview.0 → 2.3.0-preview.10

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.
@@ -1 +1 @@
1
- const a0_0x2b4509=a0_0xeda9;(function(_0x318ee9,_0x43332d){const _0x22d06b=a0_0xeda9,_0x8a1d78=_0x318ee9();while(!![]){try{const _0x5d4a54=-parseInt(_0x22d06b(0x1cc))/0x1*(parseInt(_0x22d06b(0x214))/0x2)+parseInt(_0x22d06b(0x1dc))/0x3*(parseInt(_0x22d06b(0x204))/0x4)+-parseInt(_0x22d06b(0x1dd))/0x5*(-parseInt(_0x22d06b(0x1ed))/0x6)+parseInt(_0x22d06b(0x212))/0x7+-parseInt(_0x22d06b(0x217))/0x8+parseInt(_0x22d06b(0x1fe))/0x9+-parseInt(_0x22d06b(0x1ff))/0xa;if(_0x5d4a54===_0x43332d)break;else _0x8a1d78['push'](_0x8a1d78['shift']());}catch(_0x403dab){_0x8a1d78['push'](_0x8a1d78['shift']());}}}(a0_0x51ac,0xeed51));import{EventEmitter as a0_0x1bac59}from'events';function a0_0xeda9(_0x2cb602,_0x2b14d5){_0x2cb602=_0x2cb602-0x1cb;const _0x51ac75=a0_0x51ac();let _0xeda93d=_0x51ac75[_0x2cb602];return _0xeda93d;}import{hostname as a0_0x1d6303}from'os';class T extends a0_0x1bac59{['url'];[a0_0x2b4509(0x1f9)];[a0_0x2b4509(0x21a)];[a0_0x2b4509(0x1ce)];[a0_0x2b4509(0x1e0)];[a0_0x2b4509(0x1d7)];[a0_0x2b4509(0x218)];[a0_0x2b4509(0x1fc)];[a0_0x2b4509(0x208)];[a0_0x2b4509(0x1d5)]=!0x1;constructor(_0x59a1d1){const _0x30d15e=a0_0x2b4509;super();if(this[_0x30d15e(0x20b)]=_0x59a1d1[_0x30d15e(0x20b)][_0x30d15e(0x1df)](/\/$/,''),this['alias']=_0x59a1d1[_0x30d15e(0x1f9)],this[_0x30d15e(0x21a)]=_0x59a1d1[_0x30d15e(0x21a)],this[_0x30d15e(0x1ce)]=_0x59a1d1[_0x30d15e(0x1ce)]||'sdk',this[_0x30d15e(0x1e0)]=_0x30d15e(0x20c)+_0x59a1d1['alias']+'-'+Date['now']()['toString'](0x24),this['heartbeatInterval']=_0x59a1d1[_0x30d15e(0x1d7)]??0x2bf20,this['reconnectDelay']=_0x59a1d1[_0x30d15e(0x218)]??0xbb8,_0x59a1d1[_0x30d15e(0x1d6)]!==!0x1)this[_0x30d15e(0x20d)]();}['log'](_0x13e662){const _0x563bd0=a0_0x2b4509;console[_0x563bd0(0x1f5)]('['+new Date()[_0x563bd0(0x1f0)]()[_0x563bd0(0x1eb)](0x0,0x8)+_0x563bd0(0x1fb)+this[_0x563bd0(0x1f9)]+']\x20'+_0x13e662);}async[a0_0x2b4509(0x1f7)](_0x140b3c,_0x496ccc){const _0x1043a5=a0_0x2b4509;let _0x357066={'Content-Type':_0x1043a5(0x1da),'Accept':'application/json,\x20text/event-stream'};if(this[_0x1043a5(0x21a)])_0x357066[_0x1043a5(0x1ea)]=_0x1043a5(0x20a)+this[_0x1043a5(0x21a)];let _0x1ba73e=await(await fetch(this[_0x1043a5(0x20b)]+_0x1043a5(0x20f),{'method':_0x1043a5(0x1ee),'headers':_0x357066,'body':JSON['stringify']({'jsonrpc':'2.0','id':Date[_0x1043a5(0x1fa)](),'method':'tools/call','params':{'name':_0x140b3c,'arguments':_0x496ccc}})}))['text'](),_0xa5a90e=_0x1ba73e[_0x1043a5(0x1e7)](/data: (.+)/),_0x2ecb63=_0xa5a90e?JSON[_0x1043a5(0x216)](_0xa5a90e[0x1]):JSON[_0x1043a5(0x216)](_0x1ba73e),_0x515184=_0x2ecb63?.[_0x1043a5(0x1cf)]?.[_0x1043a5(0x213)]?.[0x0]?.[_0x1043a5(0x1e9)];return _0x515184?JSON[_0x1043a5(0x216)](_0x515184):_0x2ecb63;}async[a0_0x2b4509(0x20d)](){const _0x5730ec=a0_0x2b4509;if(this['running'])return;this[_0x5730ec(0x1d5)]=!0x0,await this[_0x5730ec(0x21c)](_0x5730ec(0x207)),this[_0x5730ec(0x1f5)](_0x5730ec(0x1f8)),this[_0x5730ec(0x1fc)]=setInterval(()=>{const _0x324206=_0x5730ec;this[_0x324206(0x21c)](_0x324206(0x207))[_0x324206(0x1de)](_0xd48d87=>this[_0x324206(0x1f5)](_0x324206(0x1fd)+_0xd48d87['message']));},this[_0x5730ec(0x1d7)]),this[_0x5730ec(0x205)]();}async[a0_0x2b4509(0x1e5)](){const _0x595860=a0_0x2b4509;if(this[_0x595860(0x1d5)]=!0x1,this['sseAbort']?.[_0x595860(0x1ec)](),this[_0x595860(0x1fc)])clearInterval(this[_0x595860(0x1fc)]);await this['status'](_0x595860(0x201))[_0x595860(0x1de)](()=>{}),this['log'](_0x595860(0x206));}async['send'](_0x2b4958,_0xf9ecde,_0x458517=a0_0x2b4509(0x202)){const _0x4246e5=a0_0x2b4509;return this[_0x4246e5(0x1f7)](_0x4246e5(0x211),{'alias':_0x2b4958,'task':_0xf9ecde,'priority':_0x458517,'from_session':this['alias']});}async[a0_0x2b4509(0x1f2)](_0x22fed2,_0x46922a){const _0x35bc1f=a0_0x2b4509;return this['call']('send_message',{'alias':_0x22fed2,'message':_0x46922a,'from_session':this[_0x35bc1f(0x1f9)]});}async['reply'](_0x4138b5,_0xf1266a,_0x1a513a=a0_0x2b4509(0x21b)){const _0x269f30=a0_0x2b4509;return this['call'](_0x269f30(0x1f6),{'in_reply_to':_0x4138b5,'text':_0xf1266a,'status':_0x1a513a});}async[a0_0x2b4509(0x21c)](_0x1eff61,_0x234f27){const _0x1014ac=a0_0x2b4509;return this[_0x1014ac(0x1f7)]('report_status',{'resume_id':this[_0x1014ac(0x1e0)],'alias':this[_0x1014ac(0x1f9)],'status':_0x1eff61,'server':a0_0x1d6303(),'hostname':a0_0x1d6303(),'agent':this['agent'],'project_dir':process[_0x1014ac(0x1d2)](),..._0x234f27});}async[a0_0x2b4509(0x200)](){const _0x12a22c=a0_0x2b4509;return this[_0x12a22c(0x1f7)](_0x12a22c(0x1d3),{});}async['broadcast'](_0x36665c,_0x27b071){const _0x4ea681=a0_0x2b4509;return this[_0x4ea681(0x1f7)]('broadcast',{'message':_0x36665c,'filter_server':_0x27b071?.[_0x4ea681(0x220)],'filter_status':_0x27b071?.[_0x4ea681(0x21c)]});}async[a0_0x2b4509(0x205)](){const _0x243e9c=a0_0x2b4509;let _0x24293c=encodeURIComponent(this[_0x243e9c(0x1f9)]),_0x37bee0=this[_0x243e9c(0x20b)]+_0x243e9c(0x1db)+_0x24293c,_0x50b4b8=this[_0x243e9c(0x218)];while(this['running']){try{this[_0x243e9c(0x208)]=new AbortController();let _0x485c48={'Accept':_0x243e9c(0x1f3)};if(this[_0x243e9c(0x21a)])_0x485c48[_0x243e9c(0x1ea)]='Bearer\x20'+this[_0x243e9c(0x21a)];let _0x232c08=await fetch(_0x37bee0,{'headers':_0x485c48,'signal':this[_0x243e9c(0x208)][_0x243e9c(0x20e)]});if(!_0x232c08['ok']||!_0x232c08[_0x243e9c(0x219)]){this[_0x243e9c(0x1f5)](_0x243e9c(0x1d1)+_0x232c08[_0x243e9c(0x21c)]),await this['sleep'](_0x50b4b8),_0x50b4b8=Math[_0x243e9c(0x1e2)](_0x50b4b8*1.5,0xea60);continue;}_0x50b4b8=this[_0x243e9c(0x218)];let _0x439372=_0x232c08[_0x243e9c(0x219)]['getReader'](),_0x8128ad=new TextDecoder(),_0x5bb10f='';while(this[_0x243e9c(0x1d5)]){let {done:_0x1fbc09,value:_0x1e758b}=await _0x439372[_0x243e9c(0x21e)]();if(_0x1fbc09)break;_0x5bb10f+=_0x8128ad[_0x243e9c(0x1e4)](_0x1e758b,{'stream':!0x0});let _0xe5b50d=_0x5bb10f[_0x243e9c(0x1d8)]('\x0a');_0x5bb10f=_0xe5b50d[_0x243e9c(0x1f1)]()||'';for(let _0x1f5544 of _0xe5b50d){if(!_0x1f5544['startsWith'](_0x243e9c(0x21d)))continue;try{let _0x1e39ce=JSON[_0x243e9c(0x216)](_0x1f5544[_0x243e9c(0x1eb)](0x6));if(_0x1e39ce['type']==='connected'){this[_0x243e9c(0x1f5)](_0x243e9c(0x1cb)),this[_0x243e9c(0x1e8)](_0x243e9c(0x210));continue;}if(_0x1e39ce['type']==='new_task'||_0x1e39ce['type']==='new_message'||_0x1e39ce['type']==='broadcast')await this[_0x243e9c(0x215)]();}catch{}}}}catch(_0x55042f){if(_0x55042f[_0x243e9c(0x1d9)]===_0x243e9c(0x1cd))break;this[_0x243e9c(0x1e8)](_0x243e9c(0x203),_0x55042f),this[_0x243e9c(0x1f5)](_0x243e9c(0x1ef)+_0x55042f[_0x243e9c(0x1f2)]);}if(this[_0x243e9c(0x1d5)])this[_0x243e9c(0x1e8)]('disconnected'),this['log'](_0x243e9c(0x1e3)+_0x50b4b8/0x3e8+_0x243e9c(0x21f)),await this[_0x243e9c(0x1d4)](_0x50b4b8),_0x50b4b8=Math[_0x243e9c(0x1e2)](_0x50b4b8*1.5,0xea60);}}async[a0_0x2b4509(0x215)](){const _0x59775c=a0_0x2b4509;try{let _0x48397d=(await this[_0x59775c(0x1f7)](_0x59775c(0x1d0),{'alias':this[_0x59775c(0x1f9)],'limit':0xa}))?.[_0x59775c(0x1e1)]||[];for(let _0x198ecc of _0x48397d)await this[_0x59775c(0x1f7)](_0x59775c(0x209),{'alias':this[_0x59775c(0x1f9)],'message_id':_0x198ecc['id']}),this[_0x59775c(0x1f5)]('←\x20'+_0x198ecc[_0x59775c(0x1f4)]+':\x20'+_0x198ecc['content'][_0x59775c(0x1eb)](0x0,0x3c)),this[_0x59775c(0x1e8)]('task',_0x198ecc),this[_0x59775c(0x1e8)](_0x59775c(0x1f2),_0x198ecc);}catch(_0x49d4c0){this['log'](_0x59775c(0x1e6)+_0x49d4c0[_0x59775c(0x1f2)]);}}['sleep'](_0x54c77d){return new Promise(_0x28d263=>setTimeout(_0x28d263,_0x54c77d));}}var $=T;export{$ as default,T as CommHub};function a0_0x51ac(){const _0x1b7928=['emit','text','Authorization','slice','abort','60954RAXQAN','POST','SSE\x20error:\x20','toTimeString','pop','message','text/event-stream','from_session','log','send_reply','call','registered','alias','now',']\x20[commhub:','heartbeatTimer','heartbeat\x20failed:\x20','9988542sVTaus','12854220AxDfdK','getAllStatus','offline','normal','error','3628rxNEmB','connectSSE','disconnected','idle','sseAbort','ack_inbox','Bearer\x20','url','sdk-','connect','signal','/mcp','connected','send_task','6362188JoQPdc','content','94FeqdtI','processInbox','parse','859176YDVxqB','reconnectDelay','body','token','completed','status','data:\x20','read','s...','server','SSE\x20connected','19726mMhZmJ','AbortError','agent','result','get_inbox','SSE\x20failed:\x20','cwd','get_all_status','sleep','running','autoConnect','heartbeatInterval','split','name','application/json','/events/','771HehUqe','515QyXQmp','catch','replace','resumeId','messages','min','SSE\x20reconnecting\x20in\x20','decode','disconnect','inbox\x20error:\x20','match'];a0_0x51ac=function(){return _0x1b7928;};return a0_0x51ac();}
1
+ function a0_0x9087(_0x3f18eb,_0x38a974){_0x3f18eb=_0x3f18eb-0x1b0;const _0x527323=a0_0x5273();let _0x90874=_0x527323[_0x3f18eb];return _0x90874;}const a0_0x4139e8=a0_0x9087;(function(_0x26215d,_0x2c9eed){const _0x471321=a0_0x9087,_0x39fce5=_0x26215d();while(!![]){try{const _0x239ad1=-parseInt(_0x471321(0x1c8))/0x1*(parseInt(_0x471321(0x1b5))/0x2)+-parseInt(_0x471321(0x1fe))/0x3*(parseInt(_0x471321(0x1c2))/0x4)+-parseInt(_0x471321(0x1d3))/0x5*(parseInt(_0x471321(0x1c5))/0x6)+parseInt(_0x471321(0x200))/0x7+parseInt(_0x471321(0x1c1))/0x8+-parseInt(_0x471321(0x1e4))/0x9*(parseInt(_0x471321(0x1df))/0xa)+parseInt(_0x471321(0x1c9))/0xb;if(_0x239ad1===_0x2c9eed)break;else _0x39fce5['push'](_0x39fce5['shift']());}catch(_0x397d1a){_0x39fce5['push'](_0x39fce5['shift']());}}}(a0_0x5273,0x7bdd8));import{EventEmitter as a0_0x452b00}from'events';import{hostname as a0_0x156699}from'os';class T extends a0_0x452b00{['url'];[a0_0x4139e8(0x1b8)];[a0_0x4139e8(0x1f7)];[a0_0x4139e8(0x1e1)];[a0_0x4139e8(0x1f3)];['heartbeatInterval'];['reconnectDelay'];[a0_0x4139e8(0x1d6)];[a0_0x4139e8(0x1de)];[a0_0x4139e8(0x1be)]=!0x1;constructor(_0x1ec7b6){const _0x2dcdac=a0_0x4139e8;super();if(this[_0x2dcdac(0x1d0)]=_0x1ec7b6[_0x2dcdac(0x1d0)][_0x2dcdac(0x1ec)](/\/$/,''),this[_0x2dcdac(0x1b8)]=_0x1ec7b6[_0x2dcdac(0x1b8)],this[_0x2dcdac(0x1f7)]=_0x1ec7b6['token'],this[_0x2dcdac(0x1e1)]=_0x1ec7b6['agent']||_0x2dcdac(0x1cb),this[_0x2dcdac(0x1f3)]=_0x2dcdac(0x1dc)+_0x1ec7b6['alias']+'-'+Date[_0x2dcdac(0x1e0)]()['toString'](0x24),this[_0x2dcdac(0x201)]=_0x1ec7b6[_0x2dcdac(0x201)]??0x2bf20,this[_0x2dcdac(0x1b1)]=_0x1ec7b6[_0x2dcdac(0x1b1)]??0xbb8,_0x1ec7b6['autoConnect']!==!0x1)this[_0x2dcdac(0x1db)]();}[a0_0x4139e8(0x1ba)](_0xce487a){const _0x4d0746=a0_0x4139e8;console[_0x4d0746(0x1ba)]('['+new Date()[_0x4d0746(0x1d7)]()[_0x4d0746(0x1e6)](0x0,0x8)+_0x4d0746(0x1e2)+this['alias']+']\x20'+_0xce487a);}async['call'](_0x4fc4e6,_0x1bb33c){const _0x36dfd7=a0_0x4139e8;let _0x422b0b={'Content-Type':'application/json','Accept':'application/json,\x20text/event-stream'};if(this[_0x36dfd7(0x1f7)])_0x422b0b[_0x36dfd7(0x1c0)]=_0x36dfd7(0x1d4)+this[_0x36dfd7(0x1f7)];let _0x3e62a7=await(await fetch(this[_0x36dfd7(0x1d0)]+_0x36dfd7(0x1c3),{'method':_0x36dfd7(0x1bf),'headers':_0x422b0b,'body':JSON[_0x36dfd7(0x1ee)]({'jsonrpc':_0x36dfd7(0x1cc),'id':Date['now'](),'method':'tools/call','params':{'name':_0x4fc4e6,'arguments':_0x1bb33c}})}))[_0x36dfd7(0x1b7)](),_0x8e24e9=_0x3e62a7[_0x36dfd7(0x1ff)](/data: (.+)/),_0xe8f3df=_0x8e24e9?JSON['parse'](_0x8e24e9[0x1]):JSON['parse'](_0x3e62a7),_0x11d61f=_0xe8f3df?.['result']?.['content']?.[0x0]?.[_0x36dfd7(0x1b7)];return _0x11d61f?JSON[_0x36dfd7(0x203)](_0x11d61f):_0xe8f3df;}async['connect'](){const _0xf3262e=a0_0x4139e8;if(this[_0xf3262e(0x1be)])return;this['running']=!0x0,await this[_0xf3262e(0x1b0)]('idle'),this[_0xf3262e(0x1ba)]('registered'),this[_0xf3262e(0x1d6)]=setInterval(()=>{const _0x203fef=_0xf3262e;this['status']('idle')[_0x203fef(0x1e5)](_0x595d29=>this[_0x203fef(0x1ba)](_0x203fef(0x1f9)+_0x595d29[_0x203fef(0x1c7)]));},this[_0xf3262e(0x201)]),this[_0xf3262e(0x1b9)]();}async[a0_0x4139e8(0x1eb)](){const _0x23a6fc=a0_0x4139e8;if(this['running']=!0x1,this[_0x23a6fc(0x1de)]?.['abort'](),this[_0x23a6fc(0x1d6)])clearInterval(this[_0x23a6fc(0x1d6)]);await this[_0x23a6fc(0x1b0)](_0x23a6fc(0x1cf))['catch'](()=>{}),this[_0x23a6fc(0x1ba)](_0x23a6fc(0x1da));}async[a0_0x4139e8(0x1d9)](_0xfa460,_0x5397f3,_0x20e1ec=a0_0x4139e8(0x1ca)){const _0x22f39b=a0_0x4139e8;return this['call'](_0x22f39b(0x1f8),{'alias':_0xfa460,'task':_0x5397f3,'priority':_0x20e1ec,'from_session':this[_0x22f39b(0x1b8)]});}async[a0_0x4139e8(0x1c7)](_0xc25c42,_0x2b8571){const _0x41af61=a0_0x4139e8;return this[_0x41af61(0x1ea)]('send_message',{'alias':_0xc25c42,'message':_0x2b8571,'from_session':this['alias']});}async[a0_0x4139e8(0x1f2)](_0x2b5258,_0x514191,_0x4a53d1=a0_0x4139e8(0x1e8)){const _0x830a3c=a0_0x4139e8;return this['call'](_0x830a3c(0x1f1),{'in_reply_to':_0x2b5258,'text':_0x514191,'status':_0x4a53d1});}async['status'](_0x33e015,_0x5df1d9){const _0x1e6150=a0_0x4139e8;return this[_0x1e6150(0x1ea)]('report_status',{'resume_id':this[_0x1e6150(0x1f3)],'alias':this[_0x1e6150(0x1b8)],'status':_0x33e015,'server':a0_0x156699(),'hostname':a0_0x156699(),'agent':this[_0x1e6150(0x1e1)],'project_dir':process[_0x1e6150(0x1c4)](),..._0x5df1d9});}async[a0_0x4139e8(0x1b4)](){const _0x3f230e=a0_0x4139e8;return this[_0x3f230e(0x1ea)]('get_all_status',{});}async[a0_0x4139e8(0x1fc)](_0x4bfac2,_0x13aff5){const _0xf19557=a0_0x4139e8;return this['call'](_0xf19557(0x1fc),{'message':_0x4bfac2,'filter_server':_0x13aff5?.[_0xf19557(0x1bc)],'filter_status':_0x13aff5?.['status']});}async['connectSSE'](){const _0x3c6e5f=a0_0x4139e8;let _0x246124=encodeURIComponent(this[_0x3c6e5f(0x1b8)]),_0x321a3c=this['url']+_0x3c6e5f(0x1bd)+_0x246124,_0x49da31=this[_0x3c6e5f(0x1b1)];while(this[_0x3c6e5f(0x1be)]){try{this[_0x3c6e5f(0x1de)]=new AbortController();let _0x507414={'Accept':'text/event-stream'};if(this['token'])_0x507414['Authorization']=_0x3c6e5f(0x1d4)+this[_0x3c6e5f(0x1f7)];let _0x18bcfc=await fetch(_0x321a3c,{'headers':_0x507414,'signal':this[_0x3c6e5f(0x1de)][_0x3c6e5f(0x1ce)]});if(!_0x18bcfc['ok']||!_0x18bcfc[_0x3c6e5f(0x1fa)]){this[_0x3c6e5f(0x1ba)](_0x3c6e5f(0x1d2)+_0x18bcfc[_0x3c6e5f(0x1b0)]),await this[_0x3c6e5f(0x1d1)](_0x49da31),_0x49da31=Math[_0x3c6e5f(0x1fb)](_0x49da31*1.5,0xea60);continue;}_0x49da31=this[_0x3c6e5f(0x1b1)];let _0x1c0825=_0x18bcfc[_0x3c6e5f(0x1fa)][_0x3c6e5f(0x1d8)](),_0x45bd49=new TextDecoder(),_0x5f245d='';while(this[_0x3c6e5f(0x1be)]){let {done:_0x32689d,value:_0x5ea000}=await _0x1c0825[_0x3c6e5f(0x202)]();if(_0x32689d)break;_0x5f245d+=_0x45bd49[_0x3c6e5f(0x1cd)](_0x5ea000,{'stream':!0x0});let _0x4f5482=_0x5f245d[_0x3c6e5f(0x1e7)]('\x0a');_0x5f245d=_0x4f5482[_0x3c6e5f(0x1ed)]()||'';for(let _0x5f404f of _0x4f5482){if(!_0x5f404f['startsWith'](_0x3c6e5f(0x1c6)))continue;try{let _0x1414a2=JSON['parse'](_0x5f404f[_0x3c6e5f(0x1e6)](0x6));if(_0x1414a2[_0x3c6e5f(0x1f6)]===_0x3c6e5f(0x1bb)){this[_0x3c6e5f(0x1ba)](_0x3c6e5f(0x1fd)),this['emit'](_0x3c6e5f(0x1bb));continue;}if(_0x1414a2[_0x3c6e5f(0x1f6)]==='new_task'||_0x1414a2[_0x3c6e5f(0x1f6)]===_0x3c6e5f(0x1f0)||_0x1414a2['type']===_0x3c6e5f(0x1fc))await this['processInbox']();}catch{}}}}catch(_0x53f1fb){if(_0x53f1fb[_0x3c6e5f(0x1f5)]===_0x3c6e5f(0x1b6))break;this[_0x3c6e5f(0x1e3)]('error',_0x53f1fb),this[_0x3c6e5f(0x1ba)](_0x3c6e5f(0x1b3)+_0x53f1fb['message']);}if(this['running'])this['emit']('disconnected'),this[_0x3c6e5f(0x1ba)]('SSE\x20reconnecting\x20in\x20'+_0x49da31/0x3e8+_0x3c6e5f(0x1ef)),await this[_0x3c6e5f(0x1d1)](_0x49da31),_0x49da31=Math[_0x3c6e5f(0x1fb)](_0x49da31*1.5,0xea60);}}async[a0_0x4139e8(0x1e9)](){const _0x12fd99=a0_0x4139e8;try{let _0x289ab6=(await this[_0x12fd99(0x1ea)]('get_inbox',{'alias':this[_0x12fd99(0x1b8)],'limit':0xa}))?.['messages']||[];for(let _0x182191 of _0x289ab6)await this[_0x12fd99(0x1ea)](_0x12fd99(0x1f4),{'alias':this['alias'],'message_id':_0x182191['id']}),this[_0x12fd99(0x1ba)]('←\x20'+_0x182191['from_session']+':\x20'+_0x182191[_0x12fd99(0x1d5)]['slice'](0x0,0x3c)),this[_0x12fd99(0x1e3)](_0x12fd99(0x1dd),_0x182191),this[_0x12fd99(0x1e3)](_0x12fd99(0x1c7),_0x182191);}catch(_0x230955){this[_0x12fd99(0x1ba)](_0x12fd99(0x1b2)+_0x230955[_0x12fd99(0x1c7)]);}}['sleep'](_0x46b019){return new Promise(_0x4c2373=>setTimeout(_0x4c2373,_0x46b019));}}var $=T;function a0_0x5273(){const _0xb9b414=['resumeId','ack_inbox','name','type','token','send_task','heartbeat\x20failed:\x20','body','min','broadcast','SSE\x20connected','3kklzGc','match','4742297otxDVy','heartbeatInterval','read','parse','status','reconnectDelay','inbox\x20error:\x20','SSE\x20error:\x20','getAllStatus','120182PaDwYY','AbortError','text','alias','connectSSE','log','connected','server','/events/','running','POST','Authorization','1452400lsEPNt','325960iLhYqe','/mcp','cwd','60bWTvwm','data:\x20','message','7NFflPt','5911345IkiVmS','normal','sdk','2.0','decode','signal','offline','url','sleep','SSE\x20failed:\x20','26140bCTUFF','Bearer\x20','content','heartbeatTimer','toTimeString','getReader','send','disconnected','connect','sdk-','task','sseAbort','3346570mWRYOy','now','agent',']\x20[commhub:','emit','9fOYvkA','catch','slice','split','completed','processInbox','call','disconnect','replace','pop','stringify','s...','new_message','send_reply','reply'];a0_0x5273=function(){return _0xb9b414;};return a0_0x5273();}export{$ as default,T as CommHub};
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Outcome of one rule check against one `.gitignore` file.
3
+ */
4
+ export type EnsureRuleOutcome = "already-present" | "appended" | "created" | "skipped-no-anet";
5
+ /**
6
+ * Append `rule` to `gitignorePath` unless an exactly-matching line
7
+ * already exists. Returns the outcome. Never throws on missing parent
8
+ * directory — caller is expected to have created it (or wants the
9
+ * gitignore skipped, per the `.anet`-scoped writer below).
10
+ *
11
+ * The rule comparison is line-exact after `trim()`. Rules with leading
12
+ * `#` (comment lines in the same file) are ignored for the duplicate
13
+ * check, so an end-user can leave commented-out variants in place
14
+ * without confusing the idempotent guard.
15
+ */
16
+ export declare function ensureGitignoreRule(gitignorePath: string, rule: string): EnsureRuleOutcome;
17
+ /**
18
+ * Convenience: append multiple rules to one `.gitignore` file with one
19
+ * read/write pair. Returns the outcome per rule, in input order.
20
+ */
21
+ export declare function ensureGitignoreRules(gitignorePath: string, rules: string[]): EnsureRuleOutcome[];
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Tiny loader that mirrors what `initTelegramChannel` does at boot —
3
+ * read the parsed access.json blob and produce both the raw allowFrom
4
+ * payload (handed to the resolver verbatim) AND a boot-time warning
5
+ * string for the empty / malformed case.
6
+ *
7
+ * Extracted so wiring-level regression tests (e.g. "channel state
8
+ * built from `{allowFrom: [123]}` MUST fail-closed when a message
9
+ * arrives") run without spinning up a real telegram channel + fs
10
+ * mocks. The previous bug — `.map(String)` rewriting [123] into
11
+ * ["123"] at init time — slipped past resolver-only unit tests
12
+ * because the resolver never saw the offending shape; the channel
13
+ * state did.
14
+ */
15
+ export interface LoadedTelegramAccess {
16
+ /** Raw value from access.json — handed verbatim to resolveTelegramAccess. */
17
+ allowFromRaw: unknown;
18
+ /** Boot-time warning string when allowFrom is empty / malformed; null otherwise. */
19
+ bootWarn: string | null;
20
+ }
21
+ export declare function loadTelegramAccess(opts: {
22
+ channelDir: string;
23
+ parsedAccess: {
24
+ allowFrom?: unknown;
25
+ } | null | undefined;
26
+ }): LoadedTelegramAccess;
27
+ /**
28
+ * Discriminated reason for an access decision. Callers can switch on
29
+ * `kind` for routing (e.g. `empty-fail-closed` warrants a louder boot
30
+ * warning than a simple `denied` mid-stream).
31
+ */
32
+ export type AccessKind = "wildcard-allow" | "explicit-allow" | "empty-fail-closed" | "denied";
33
+ export interface AccessDecision {
34
+ allow: boolean;
35
+ kind: AccessKind;
36
+ /** Short, machine-readable reason for logging. */
37
+ reason: string;
38
+ }
39
+ /**
40
+ * Normalise an allowFrom input into a deduped string[] suitable for
41
+ * the lookup checks. Accepts: a real string[] (the happy case), null /
42
+ * undefined (treated as empty), a non-array shape (defensive — treated
43
+ * as empty + reason surfaced).
44
+ */
45
+ export declare function normalizeAllowFrom(raw: unknown): {
46
+ list: string[];
47
+ malformed: boolean;
48
+ };
49
+ /**
50
+ * Resolve whether an inbound Telegram message should be processed.
51
+ *
52
+ * Fail-closed: empty / missing / malformed `allowFrom` always denies.
53
+ * Wildcard `"*"` always allows. Otherwise the sender's id OR username
54
+ * (after non-empty check) must match a list entry.
55
+ *
56
+ * @param opts.allowFrom the access.json `allowFrom` array
57
+ * @param opts.senderId numeric Telegram user id as string
58
+ * (callers already coerce; we trust)
59
+ * @param opts.senderUsername optional `@username` (no `@` prefix)
60
+ */
61
+ export declare function resolveTelegramAccess(opts: {
62
+ allowFrom: unknown;
63
+ senderId: string;
64
+ senderUsername?: string | null;
65
+ }): AccessDecision;
66
+ /**
67
+ * Resolve whether an inbound Feishu/Lark event should be processed.
68
+ *
69
+ * Same fail-closed contract as telegram. DM path checks `allowFrom`;
70
+ * group path checks `allowChats` first, then `groupPolicy` decides
71
+ * whether listed chats trigger the agent (`all` → trigger any message;
72
+ * `observe` → never trigger; `mention` → only on @-mention — caller
73
+ * still has to honour that flag, this helper just returns allow=true).
74
+ *
75
+ * @param opts.conversationType "dm" | "group" | "channel" | "thread"
76
+ * @param opts.allowFrom access.json `allowFrom` array
77
+ * @param opts.allowChats access.json `allowChats` array
78
+ * @param opts.senderId feishu open_id of the sender
79
+ * @param opts.conversationId feishu chat_id of the conversation
80
+ * @param opts.groupPolicy "all" | "mention" | "observe"
81
+ */
82
+ export declare function resolveFeishuAccess(opts: {
83
+ conversationType: "dm" | "group" | "channel" | "thread" | string;
84
+ allowFrom: unknown;
85
+ allowChats: unknown;
86
+ senderId: string;
87
+ conversationId: string;
88
+ groupPolicy: "all" | "mention" | "observe" | string;
89
+ }): AccessDecision;
90
+ /**
91
+ * Build the boot-time warning string when a channel starts with an
92
+ * empty / malformed allowlist. Caller emits this via `warn()` once at
93
+ * startup so operators see the fail-closed posture immediately rather
94
+ * than discovering it the first time a message gets denied.
95
+ *
96
+ * Returns `null` when the allowlist has at least one entry (no boot
97
+ * warning needed).
98
+ */
99
+ export declare function buildEmptyAllowlistWarn(opts: {
100
+ channel: string;
101
+ channelDir: string;
102
+ allowFrom: unknown;
103
+ }): string | null;
@@ -4,7 +4,7 @@ export declare class FeishuAdapter implements IMAdapter {
4
4
  readonly platform = "feishu";
5
5
  readonly ingressMode: IMIngressMode;
6
6
  private feishuConfig;
7
- private connectionName;
7
+ private connectionName_;
8
8
  private client;
9
9
  private wsClient;
10
10
  /**
@@ -17,6 +17,23 @@ export declare class FeishuAdapter implements IMAdapter {
17
17
  /** Where to persist downloaded inbound media (M5c). */
18
18
  private mediaDir;
19
19
  private health_;
20
+ /**
21
+ * Snapshot of the current `access.allowFrom` list (from access.json).
22
+ * Used by the bridge's rate-limiter to exempt operator-vouched explicit
23
+ * sender ids from the DM flood limit (2026-06-29: Vincent's multi-turn
24
+ * heavy work was tripping the 3-msg/60s DM limit; explicit-listed
25
+ * users are already operator-trusted via the access whitelist, no
26
+ * need to also flood-limit them). Returns `[]` before `init()`. The
27
+ * wildcard `["*"]` allowlist does NOT count as "explicit" — that's
28
+ * the public-channel shape and still needs flood protection.
29
+ */
30
+ getAllowFrom(): readonly string[];
31
+ /**
32
+ * Read-only accessor used by bridge to resolve per-connection paths
33
+ * (RFC-020 §15 outbound-marker validation needs the connection name
34
+ * to build the allowed per-conversation directory prefix).
35
+ */
36
+ get connectionName(): string;
20
37
  init(config: IMChannelConfig): Promise<void>;
21
38
  start(onEvent: OnEventHandler): Promise<void>;
22
39
  stop(): Promise<void>;
@@ -26,4 +43,55 @@ export declare class FeishuAdapter implements IMAdapter {
26
43
  edit(_target: IMConversationRef, messageId: string, message: NormalizedIMMessage): Promise<void>;
27
44
  health(): IMAdapterHealth;
28
45
  }
46
+ /**
47
+ * Heuristic — does this text look like it contains markdown syntax that
48
+ * Feishu's plain-text `msg_type:"text"` would render as literal source?
49
+ * Catches the patterns Vincent's heavy work produces: tables (`|...|`
50
+ * + separator row), fenced code blocks (`` ``` ``), ATX headings (`#`),
51
+ * bold/italic (`**text**` / `*text*`), unordered/ordered lists, inline
52
+ * code (`` `code` ``), markdown links (`[label](url)`).
53
+ *
54
+ * Returns true for at least one match; the adapter then upgrades to
55
+ * `msg_type:"interactive"` with a `markdown` element. Returns false for
56
+ * plain prose so the text path stays untouched (no perf cost, no
57
+ * behavior change for non-markdown replies).
58
+ *
59
+ * Conservative — single-character matches (e.g., a `|` in prose, one
60
+ * `*` for emphasis-of-one-word that Feishu would render OK as text)
61
+ * are NOT enough to trigger. We want false-positives < false-negatives
62
+ * (a false-positive upgrade renders fine; a false-negative shows raw
63
+ * `|` and `**`).
64
+ */
65
+ export declare function looksLikeMarkdown(text: string): boolean;
66
+ /**
67
+ * Parse Feishu `message_type: "post"` content into plain text. Post
68
+ * content is a nested structure:
69
+ *
70
+ * { title?: string,
71
+ * content: Array<Array<{ tag: "text"|"img"|"a"|"at"|"emotion", ... }>>
72
+ * }
73
+ *
74
+ * Each top-level array entry is a paragraph; each paragraph is an array
75
+ * of typed segments. We flatten by:
76
+ * - prepending title (if present) as `<title>\n\n`
77
+ * - joining paragraphs with `\n\n`
78
+ * - joining segments within a paragraph in order
79
+ * - tag=text → emit segment.text as-is
80
+ * - tag=a → emit `[label](href)` (markdown link)
81
+ * - tag=at → emit `@user_name` (fallback to `@<user_id>` if no name)
82
+ * - tag=img → emit `[图片]` placeholder (actual download via maybeAttachImages)
83
+ * - tag=emotion → emit `[emoji]`
84
+ * - unknown tag → skip
85
+ *
86
+ * @internal exported for unit tests.
87
+ */
88
+ export declare function parsePostContent(rawJson: string): string;
89
+ /**
90
+ * Walk a Feishu `post` content JSON and collect all `image_key` values
91
+ * from `tag: "img"` segments. Used by `maybeAttachImages` to schedule
92
+ * downloads for every image in a 图文混排 message.
93
+ *
94
+ * @internal exported for unit tests.
95
+ */
96
+ export declare function extractPostImageKeys(rawJson: string): string[];
29
97
  export {};
@@ -49,6 +49,13 @@ export interface FeishuBridgeOptions {
49
49
  export interface BridgeIncomingEnvelope {
50
50
  type: "event";
51
51
  event: NormalizedIMEvent;
52
+ /** Canonical outbound directory for this conversation (RFC-020 §15.1).
53
+ * Single source of truth — the agent-node injects this verbatim into
54
+ * the system prompt's "save files here" instruction, and the bridge
55
+ * whitelist accepts files only under this directory. Computed by the
56
+ * bridge from `event.conversation.conversationId` + `adapter
57
+ * .connectionName`. Trailing slash included. */
58
+ outboundDir?: string;
52
59
  }
53
60
  /** Parent → bridge: agent reply text for a previously-forwarded event. */
54
61
  export interface BridgeReplyEnvelope {
@@ -0,0 +1,61 @@
1
+ /**
2
+ * RFC-020 §14 — markdown → image rendering for Feishu replies.
3
+ *
4
+ * Vincent 2026-06-29 path: Feishu's `markdown` card element doesn't
5
+ * render ATX headings or GFM tables (preview.7 caught only bold / list
6
+ * / link). Rather than partially support a moving subset of card
7
+ * elements, we render structured markdown to a PNG via headless
8
+ * chromium and send it through Feishu's image API (`im:resource:upload`
9
+ * scope). Pixel-perfect fidelity, zero schema fragility, text-not-
10
+ * copyable accepted as the tradeoff for "actually renders".
11
+ *
12
+ * Hybrid route (decided in adapter.send):
13
+ * - plain text (no markdown markers) → msg_type:"text"
14
+ * - markdown WITHOUT heading / table / long → msg_type:"interactive" (schema 1.0 card with `markdown` element — keeps copy/paste for short bold/list/link replies; preview.7 path)
15
+ * - markdown WITH heading / table / long → THIS PATH (msg_type:"image" with rendered PNG)
16
+ *
17
+ * Renderer choice: `puppeteer-core` (no bundled chromium) + system
18
+ * chromium (apt install in Docker). Rationale:
19
+ * - Pure-JS canvas-layout libraries (node-canvas, @napi-rs/canvas)
20
+ * cost 4-6h of manual paragraph wrapping + table cell measurement
21
+ * + Chinese width metrics; chromium does this natively.
22
+ * - puppeteer-core (5MB) + system chromium (~100MB) is smaller than
23
+ * full puppeteer (170MB) which bundles its own chromium.
24
+ * - Headless screenshot ~500ms after warmup. Bot's heavy turn is 20-
25
+ * 70s; rendering cost is in the noise.
26
+ *
27
+ * Chromium reuse: we keep a single browser instance hot across
28
+ * renderings to avoid the ~2-3s cold-start per call. Auto-close on
29
+ * idle is a follow-up — agent-node worker lifetime is bounded.
30
+ */
31
+ import { Buffer } from "node:buffer";
32
+ /**
33
+ * Close the shared browser. Called from adapter.stop() during graceful
34
+ * worker shutdown. Subsequent render calls re-launch.
35
+ */
36
+ export declare function closeBrowser(): Promise<void>;
37
+ /**
38
+ * Render markdown text to a PNG buffer.
39
+ *
40
+ * Width is fixed at 800px (IM-friendly); height auto-fits content via
41
+ * `fullPage:true`. Returns the raw PNG bytes — caller hands them to
42
+ * `lark.im.image.create({image: Readable.from(buffer)})`.
43
+ *
44
+ * Throws on chromium launch failure or page navigation failure.
45
+ * Caller is responsible for fallback (e.g., send the raw text as a
46
+ * code block in the schema 1.0 card path).
47
+ */
48
+ export declare function renderMarkdownToPng(text: string): Promise<Buffer>;
49
+ /**
50
+ * Decide whether `renderMarkdownToPng` should be invoked for this reply
51
+ * text, vs. falling back to msg_type:"text" / msg_type:"interactive"
52
+ * (schema 1.0 card). Locked by 通信龙 3f70044c — trigger image when:
53
+ * - text contains a markdown table (Feishu card can't render)
54
+ * - text contains an ATX heading (Feishu card can't render)
55
+ * - text length > 2000 chars (image is better than scrollable text wall)
56
+ *
57
+ * Returns false for short prose, single-line plain text, and short
58
+ * markdown lists / bold / link / inline-code which DO render fine in
59
+ * the schema 1.0 `markdown` card element.
60
+ */
61
+ export declare function shouldRenderAsImage(text: string): boolean;
@@ -0,0 +1,140 @@
1
+ /**
2
+ * RFC-020 §15 — agent→bridge outbound file protocol.
3
+ *
4
+ * 2026-06-29 Vincent UAT: agent generated a PDF via reportlab + wrote it
5
+ * to `/work/feishu-attachments/<conv>/x.pdf`, then in text asked Vincent
6
+ * "I'm not sure if the system will auto-attach this — try another way?"
7
+ * The bridge has `adapter.send({files})` since #329, but no protocol
8
+ * told the agent how to signal "dispatch this file to the user".
9
+ *
10
+ * Convention: in its final reply, the agent emits one
11
+ *
12
+ * [[send-file:/abs/path]]
13
+ *
14
+ * marker per file (one per line, end of reply). The bridge:
15
+ *
16
+ * 1. extracts every `[[send-file:...]]` token,
17
+ * 2. strips them from the user-visible text,
18
+ * 3. validates each path against an allowed-outbound-root whitelist
19
+ * (`/work/feishu-attachments/<conn>/<chat>/` — same per-conversation
20
+ * directory the LLM has Write access to under Layer B),
21
+ * 4. checks the file exists, is non-empty, and ≤ 30 MB (Feishu file_v1
22
+ * upper bound),
23
+ * 5. magic-byte sniffs to choose `image_key` vs `file_key` upload route,
24
+ * 6. dispatches each file via `adapter.send({files:[...]})`,
25
+ * 7. emits a friendly fallback if validation fails — never the raw error.
26
+ *
27
+ * Pure module — no I/O, no IM SDK calls. The parser returns the cleaned
28
+ * text + a structured list of file requests; bridge.ts is responsible
29
+ * for filesystem checks and dispatching.
30
+ */
31
+ /**
32
+ * One outbound-file request parsed from an agent reply.
33
+ *
34
+ * - `raw`: the bytes between `[[send-file:` and `]]`, before any
35
+ * normalization. Kept for stderr forensic.
36
+ * - `normalized`: lexical-normalized absolute path (quote-strip, `..`
37
+ * collapse, `//` collapse, `./` resolution against cwd). The
38
+ * `validatedAbsolute` field is set later by the bridge after the
39
+ * realpath / whitelist / exists check.
40
+ */
41
+ export interface OutboundFileRequest {
42
+ raw: string;
43
+ normalized: string;
44
+ }
45
+ export interface MarkerParseResult {
46
+ /** Reply text with all `[[send-file:...]]` markers removed + adjacent
47
+ * whitespace collapsed. May be empty (file-only reply). */
48
+ cleanedText: string;
49
+ /** Files in source order. Caller-side validation happens after. */
50
+ files: OutboundFileRequest[];
51
+ }
52
+ /**
53
+ * Lexically normalize a path token from the marker. NOT a full
54
+ * symlink/realpath resolver — bridge does that after this returns.
55
+ *
56
+ * - Trim leading / trailing whitespace.
57
+ * - Strip one layer of matching quotes (`"/x"` / `'/x'` → `/x`).
58
+ * - `.` and `..` segments collapse via `path.posix.normalize`.
59
+ * - Repeated `/` collapse.
60
+ *
61
+ * Returns an absolute path or the empty string if the input cannot be
62
+ * interpreted as an absolute path token (caller drops empties).
63
+ */
64
+ export declare function normalizeMarkerPath(raw: string): string;
65
+ /**
66
+ * Parse an agent reply into cleaned text + outbound-file requests.
67
+ *
68
+ * Empty / null input returns `{cleanedText: "", files: []}` so callers
69
+ * can safely pipe through without defensive checks.
70
+ *
71
+ * Marker-only reply (no surrounding text) returns `cleanedText: ""` —
72
+ * caller should NOT send an empty text message in that case; they only
73
+ * dispatch the files.
74
+ *
75
+ * Multiple markers on one reply are all captured. Adjacent whitespace
76
+ * around stripped markers is collapsed (no double blank lines).
77
+ */
78
+ export declare function parseOutboundMarkers(reply: string | null | undefined): MarkerParseResult;
79
+ export { OUTBOUND_ROOT, feishuConvKey, feishuOutboundDir } from "./outbound-paths.js";
80
+ /**
81
+ * Validate a normalized absolute path against the per-conversation
82
+ * whitelist + size + existence checks. Returns null on success or a
83
+ * Chinese user-friendly reason on failure (the same string can be
84
+ * surfaced as a fallback text reply when ALL files fail validation).
85
+ *
86
+ * Filesystem-touching — bridge calls this AFTER `parseOutboundMarkers`.
87
+ *
88
+ * Two API shapes accepted (overload):
89
+ *
90
+ * 1. **Preferred**: caller passes `expectedDir` — the precomputed
91
+ * `/work/feishu-attachments/<connectionName>/<convKey>/` value
92
+ * from `feishuOutboundDir`. This guarantees what the agent was
93
+ * TOLD to use (via cli.ts prompt injection) matches what we
94
+ * ACCEPT here — no second algorithm.
95
+ *
96
+ * 2. **Legacy**: caller passes `connectionName` + `convKey`. We
97
+ * compute the prefix here via the same helper. Kept for the
98
+ * single-arg unit-test ergonomics + backwards-compat (the old
99
+ * bridge wire-up shape).
100
+ *
101
+ * Params:
102
+ * - p: normalized absolute path candidate
103
+ * - expectedDir: precomputed whitelist directory (trailing `/`)
104
+ * OR
105
+ * - convKey + connectionName: legacy two-piece form
106
+ * - statFn: optional override for tests (defaults to fs.statSync)
107
+ * - realpathFn: optional override for tests (defaults to fs.realpathSync)
108
+ *
109
+ * The whitelist check uses literal `.startsWith(expectedDir)` — the
110
+ * trailing slash on `expectedDir` ensures `oc_abc/` doesn't accept
111
+ * paths under `oc_abc_evil/`.
112
+ */
113
+ export interface ValidateOpts {
114
+ p: string;
115
+ /** Precomputed prefix (with trailing `/`). Preferred over convKey+connectionName. */
116
+ expectedDir?: string;
117
+ convKey?: string;
118
+ connectionName?: string;
119
+ statFn?: (p: string) => {
120
+ size: number;
121
+ };
122
+ realpathFn?: (p: string) => string;
123
+ }
124
+ export declare function validateOutboundPath(opts: ValidateOpts): string | null;
125
+ /**
126
+ * Magic-byte sniffer — picks `image` vs `file` upload route. Reads the
127
+ * first 16 bytes only; safe to call before commitment. Returns:
128
+ *
129
+ * - "image" when the bytes are a PNG / JPG / GIF / WebP / BMP signature
130
+ * (Feishu image_v1 supports these natively, with chat inline render).
131
+ * - "file" otherwise (PDF, TXT, audio, video, archive, …).
132
+ *
133
+ * Caller supplies a Buffer (bridge reads via fs.openSync + readSync).
134
+ */
135
+ export declare function sniffFileKind(buf: Buffer): "image" | "file";
136
+ /**
137
+ * User-facing fallback message when ALL marker validations fail and the
138
+ * stripped text is empty (would leave the user with no reply at all).
139
+ */
140
+ export declare const ALL_FILES_FAILED_FALLBACK = "[\u6587\u4EF6\u9644\u4EF6\u53D1\u9001\u5931\u8D25] \u6211\u51C6\u5907\u597D\u4E86\u6587\u4EF6\u4F46\u5206\u53D1\u65F6\u51FA\u4E86\u95EE\u9898\u2014\u2014\u7A0D\u540E\u518D\u8BD5\u6216\u6362\u79CD\u65B9\u5F0F\u3002";
@@ -0,0 +1,50 @@
1
+ /**
2
+ * RFC-020 §15.1 — feishu outbound/attachment path helpers (SHARED).
3
+ *
4
+ * Single source of truth for `/work/feishu-attachments/<conn>/<convKey>/`
5
+ * naming. Used by:
6
+ *
7
+ * - `adapter.ts` inbound `downloadImage` (subdir for received images),
8
+ * - `bridge.ts` outbound marker whitelist (where the agent may write),
9
+ * - `worker.ts` outbound envelope (the concrete path injected into the
10
+ * agent's prompt, so what we tell the agent matches what we accept).
11
+ *
12
+ * 2026-06-29 Vincent UAT: prior to this module, the inbound downloader
13
+ * used `conversationId.replace(/[^a-zA-Z0-9_-]/g, "_")` and the outbound
14
+ * bridge whitelist used `sender.id` for DMs / `chatId` for groups. The
15
+ * two algorithms diverged — agent learned the inbound oc_<chat>/ layout
16
+ * from incoming attachments, then wrote outbound files to the same
17
+ * oc_<chat>/ subdir, but the bridge required ou_<userid>/. Every PDF
18
+ * generated by the bot was rejected. This module unifies on
19
+ * `conversationId` (the open_chat_id assigned by Feishu — present for
20
+ * DMs and groups equally).
21
+ */
22
+ /**
23
+ * Sanitize a raw Feishu conversation id (`oc_<hex>` / occasionally
24
+ * `om_<id>` for DMs) into a filesystem-safe segment. Identical to the
25
+ * adapter.ts inbound download path policy — DO NOT CHANGE without also
26
+ * migrating any on-disk attachments under the old key.
27
+ *
28
+ * The regex strips anything outside `[A-Za-z0-9_-]`, replacing with `_`.
29
+ * Falsy / non-string input returns the empty string; caller is
30
+ * responsible for substituting a fallback (the empty key would resolve
31
+ * to `/work/feishu-attachments/<conn>//` which the path-prefix
32
+ * whitelist rejects cleanly).
33
+ */
34
+ export declare function feishuConvKey(rawId: string | undefined | null): string;
35
+ /**
36
+ * Compute the canonical per-conversation directory for outbound file
37
+ * dispatch + inbound attachment downloads. Trailing slash included so
38
+ * `.startsWith(...)` whitelist checks are unambiguous about the
39
+ * conversation boundary (no `oc_abc` matching `oc_abc_x`).
40
+ *
41
+ * Example:
42
+ * feishuOutboundDir("feishu-local", "oc_9a7eedbba275a87999f7ee2cfb10f4cb")
43
+ * → "/work/feishu-attachments/feishu-local/oc_9a7eedbba275a87999f7ee2cfb10f4cb/"
44
+ */
45
+ export declare function feishuOutboundDir(connectionName: string, rawConvId: string | undefined | null): string;
46
+ /**
47
+ * The fixed prefix root used as a sanity check by the marker validator.
48
+ * Useful for tests + for `validateOutboundPath` defense-in-depth.
49
+ */
50
+ export declare const OUTBOUND_ROOT = "/work/feishu-attachments";